///|
pub(all) struct PipelineInput {
  cantus : Array[Note]
  counterpoint : Array[Note]
  meter : Meter
  profile : RuleProfileConfig
} derive(Debug, Eq, ToJson)

///|
pub(all) struct PipelineOutput {
  validation : PairValidation
  rhythm : RhythmSummary
  contour : ContourSummary
  harmony : HarmonySummary
  motion : VoiceLeadingSummary
  report : ReportDocument
  recommendations : Array[Recommendation]
} derive(Debug, Eq, ToJson)

///|
pub fn PipelineInput::new(
  cantus~ : Array[Note],
  counterpoint~ : Array[Note],
  meter~ : Meter,
  profile~ : RuleProfileConfig,
) -> PipelineInput {
  { cantus, counterpoint, meter, profile }
}

///|
pub fn run_pipeline(input : PipelineInput) -> PipelineOutput {
  let validation = validate_pair(
    input.cantus,
    input.counterpoint,
    input.profile,
  )
  let analysis = validation.analysis
  {
    validation,
    rhythm: summarize_rhythm(input.meter, input.counterpoint),
    contour: contour_summary(input.counterpoint),
    harmony: harmony_summary(input.cantus, input.counterpoint),
    motion: summarize_voice_leading(
      input.cantus,
      input.counterpoint,
      MotionConstraint::classroom(),
    ),
    report: report_document(
      "Counterpoint pipeline",
      analysis,
      input.meter,
      input.cantus,
      input.counterpoint,
    ),
    recommendations: recommendations(analysis),
  }
}

///|
pub fn pipeline_passed(output : PipelineOutput) -> Bool {
  output.validation.passed() &&
  output.rhythm.notes > 0 &&
  output.motion.rejected == 0
}

///|
pub fn pipeline_score(output : PipelineOutput) -> Int {
  let base = output.validation.quality_score()
  let motion_penalty = output.motion.rejected * 5
  let tension_penalty = output.harmony.dissonant * 2
  cap_quality(base - motion_penalty - tension_penalty)
}

///|
pub fn pipeline_grade(output : PipelineOutput) -> String {
  let score = pipeline_score(output)
  if score >= 90 {
    "excellent"
  } else if score >= 75 {
    "good"
  } else if score >= 60 {
    "review"
  } else {
    "revise"
  }
}

///|
pub fn pipeline_summary(output : PipelineOutput) -> String {
  "grade=\{pipeline_grade(output)} score=\{pipeline_score(output)} notes=\{output.rhythm.notes} findings=\{output.validation.summary.total}"
}

///|
pub fn pipeline_markdown(output : PipelineOutput) -> String {
  let lines : Array[String] = [
    "# Counterpoint pipeline",
    "",
    "- Grade: \{pipeline_grade(output)}",
    "- Score: \{pipeline_score(output)}",
    "- Notes: \{output.rhythm.notes}",
    "- Findings: \{output.validation.summary.total}",
    "",
    render_document(output.report),
    "",
    recommendations_markdown(output.validation.analysis),
  ]
  lines.join("\n")
}

///|
pub fn pipeline_has_rule(output : PipelineOutput, rule : RuleKind) -> Bool {
  analysis_has_rule(output.validation.analysis, rule)
}

///|
pub fn pipeline_rule_count(output : PipelineOutput, rule : RuleKind) -> Int {
  finding_count(output.validation.analysis, rule)
}

///|
pub fn pipeline_recommendation_count(output : PipelineOutput) -> Int {
  output.recommendations.length()
}

///|
pub fn pipeline_report_section_count(output : PipelineOutput) -> Int {
  report_section_count(output.report)
}

///|
pub fn pipeline_is_explainable(output : PipelineOutput) -> Bool {
  pipeline_report_section_count(output) >= 5 &&
  pipeline_recommendation_count(output) == output.validation.summary.total
}

///|
pub fn pipeline_profile(output : PipelineOutput) -> String {
  output.validation.profile
}

///|
pub fn pipeline_quality_flags(output : PipelineOutput) -> Array[String] {
  let result : Array[String] = []
  if output.validation.cantus.issues.length() > 0 {
    result.push("cantus-timeline")
  }
  if output.validation.counterpoint.issues.length() > 0 {
    result.push("counterpoint-timeline")
  }
  if output.harmony.crossings > 0 {
    result.push("voice-crossing")
  }
  if output.harmony.dissonant > 0 {
    result.push("dissonance")
  }
  if output.motion.rejected > 0 {
    result.push("motion")
  }
  if output.rhythm.syncopations > 0 {
    result.push("syncopation")
  }
  result
}

///|
pub fn pipeline_has_quality_flag(
  output : PipelineOutput,
  flag : String,
) -> Bool {
  for value in pipeline_quality_flags(output) {
    if value == flag {
      return true
    }
  }
  false
}

///|
pub fn pipeline_manifest(output : PipelineOutput) -> Array[String] {
  let flags = pipeline_quality_flags(output).join(",")
  [
    "profile=\{pipeline_profile(output)}",
    "grade=\{pipeline_grade(output)}",
    "score=\{pipeline_score(output)}",
    "explainable=\{pipeline_is_explainable(output)}",
    "flags=\{flags}",
  ]
}

///|
pub fn pipeline_manifest_text(output : PipelineOutput) -> String {
  pipeline_manifest(output).join("\n")
}

///|
pub fn pipeline_compare(left : PipelineOutput, right : PipelineOutput) -> Int {
  pipeline_score(left) - pipeline_score(right)
}

///|
pub fn pipeline_with_profile(
  input : PipelineInput,
  profile : RuleProfileConfig,
) -> PipelineOutput {
  run_pipeline({ ..input, profile, })
}

///|
pub fn pipeline_profiles(input : PipelineInput) -> Array[PipelineOutput] {
  let result : Array[PipelineOutput] = []
  for profile in valid_profiles() {
    result.push(pipeline_with_profile(input, profile))
  }
  result
}

///|
pub fn best_pipeline(input : PipelineInput) -> PipelineOutput {
  let outputs = pipeline_profiles(input)
  let mut best = outputs[0]
  for output in outputs {
    if pipeline_score(output) > pipeline_score(best) {
      best = output
    }
  }
  best
}