///|
pub(all) struct SupportEntry {
  encoding : String
  bits_per_sample : Int
  can_parse : Bool
  can_decode : Bool
  can_encode : Bool
  notes : String
} derive(Eq, @debug.Debug)

///|
pub fn SupportEntry::new(
  encoding : String,
  bits_per_sample : Int,
  can_parse : Bool,
  can_decode : Bool,
  can_encode : Bool,
  notes : String,
) -> SupportEntry {
  { encoding, bits_per_sample, can_parse, can_decode, can_encode, notes }
}

///|
pub fn SupportEntry::status(self : SupportEntry) -> String {
  if self.can_parse && self.can_decode {
    "supported"
  } else if self.can_parse {
    "metadata-only"
  } else {
    "unsupported"
  }
}

///|
pub fn supported_format_matrix() -> Array[SupportEntry] {
  [
    SupportEntry::new("PCM unsigned", 8, true, true, true, "common 8-bit WAV"),
    SupportEntry::new(
      "PCM signed", 16, true, true, true, "most common uncompressed WAV",
    ),
    SupportEntry::new(
      "PCM signed", 24, true, true, true, "studio/intermediate assets",
    ),
    SupportEntry::new(
      "PCM signed", 32, true, true, true, "integer high dynamic range",
    ),
    SupportEntry::new(
      "IEEE Float", 32, true, true, false, "decode supported; encoder v1 writes PCM",
    ),
    SupportEntry::new(
      "A-law", 8, true, false, false, "recognized but intentionally not decoded",
    ),
    SupportEntry::new(
      "mu-law", 8, true, false, false, "recognized but intentionally not decoded",
    ),
    SupportEntry::new(
      "Extensible", 0, true, false, false, "planned after base PCM scope",
    ),
  ]
}

///|
pub fn support_matrix_to_text() -> String {
  for entry in supported_format_matrix(); text = "MoonWavKit support matrix" {
    continue "\{text}\n\{entry.encoding} \{entry.bits_per_sample}-bit: \{entry.status()} - \{entry.notes}"
  } nobreak {
    text
  }
}

///|
pub(all) enum FixtureExpectation {
  FixtureShouldPass
  FixtureShouldFailValidation
  FixtureShouldFailDecode
} derive(Eq, @debug.Debug)

///|
pub fn FixtureExpectation::label(self : FixtureExpectation) -> String {
  match self {
    FixtureShouldPass => "pass"
    FixtureShouldFailValidation => "validation-fail"
    FixtureShouldFailDecode => "decode-fail"
  }
}

///|
pub(all) struct FixtureCase {
  name : String
  bytes : Array[Int]
  expectation : FixtureExpectation
  expected_error : String
} derive(Eq, @debug.Debug)

///|
pub fn FixtureCase::new(
  name : String,
  bytes : Array[Int],
  expectation : FixtureExpectation,
  expected_error? : String = "",
) -> FixtureCase {
  { name, bytes, expectation, expected_error }
}

///|
pub(all) struct FixtureCaseResult {
  name : String
  passed : Bool
  expectation : String
  actual_error : String
  frames : Int
  channels : Int
  sample_rate : Int
} derive(Eq, @debug.Debug)

///|
pub fn FixtureCaseResult::new(
  name : String,
  passed : Bool,
  expectation : String,
  actual_error : String,
  frames : Int,
  channels : Int,
  sample_rate : Int,
) -> FixtureCaseResult {
  { name, passed, expectation, actual_error, frames, channels, sample_rate }
}

///|
pub fn fixture_catalog() -> Array[FixtureCase] {
  [
    FixtureCase::new("pcm16-mono", fixture_pcm16_mono(), FixtureShouldPass),
    FixtureCase::new("pcm8-stereo", fixture_pcm8_stereo(), FixtureShouldPass),
    FixtureCase::new("pcm24-mono", fixture_pcm24_mono(), FixtureShouldPass),
    FixtureCase::new("pcm32-mono", fixture_pcm32_mono(), FixtureShouldPass),
    FixtureCase::new("float32-mono", fixture_float32_mono(), FixtureShouldPass),
    FixtureCase::new("info-cue", fixture_with_info_and_cue(), FixtureShouldPass),
    FixtureCase::new(
      "unknown-chunk",
      fixture_with_unknown_chunk(),
      FixtureShouldPass,
    ),
    FixtureCase::new(
      "not-riff",
      fixture_not_riff(),
      FixtureShouldFailValidation,
      expected_error="not-riff",
    ),
    FixtureCase::new(
      "missing-data",
      fixture_missing_data(),
      FixtureShouldFailValidation,
      expected_error="missing-data",
    ),
    FixtureCase::new(
      "unsupported-alaw",
      fixture_unsupported_alaw(),
      FixtureShouldFailValidation,
      expected_error="unsupported-format",
    ),
    FixtureCase::new(
      "truncated-data",
      fixture_truncated_data(),
      FixtureShouldFailValidation,
      expected_error="truncated-data",
    ),
  ]
}

///|
pub fn run_fixture_case(case : FixtureCase) -> FixtureCaseResult {
  let validation = validate_wav(case.bytes)
  if validation.is_error() {
    let actual = validation.code()
    let passed = case.expectation == FixtureShouldFailValidation &&
      (case.expected_error.length() == 0 || case.expected_error == actual)
    FixtureCaseResult::new(
      case.name,
      passed,
      case.expectation.label(),
      actual,
      0,
      0,
      0,
    )
  } else {
    let decoded = parse_and_decode(case.bytes)
    if !decoded.ok {
      let actual = decoded.error.code()
      let passed = case.expectation == FixtureShouldFailDecode &&
        (case.expected_error.length() == 0 || case.expected_error == actual)
      FixtureCaseResult::new(
        case.name,
        passed,
        case.expectation.label(),
        actual,
        0,
        0,
        0,
      )
    } else {
      FixtureCaseResult::new(
        case.name,
        case.expectation == FixtureShouldPass,
        case.expectation.label(),
        "",
        decoded.float_buffer.frame_count(),
        decoded.float_buffer.channels,
        decoded.float_buffer.sample_rate,
      )
    }
  }
}

///|
pub fn run_fixture_catalog() -> Array[FixtureCaseResult] {
  let cases = fixture_catalog()
  Array::makei(cases.length(), i => run_fixture_case(cases[i]))
}

///|
pub fn fixture_catalog_passed() -> Bool {
  for result in run_fixture_catalog() {
    if !result.passed {
      break false
    }
  } nobreak {
    true
  }
}

///|
pub fn fixture_catalog_report() -> String {
  for result in run_fixture_catalog(); text = "MoonWavKit fixture catalog" {
    let status = if result.passed { "PASS" } else { "FAIL" }
    continue "\{text}\n\{status} \{result.name} expectation=\{result.expectation} error=\{result.actual_error} frames=\{result.frames}"
  } nobreak {
    text
  }
}