///|
pub(all) enum LessonStage {
  Listen
  Identify
  Repair
  Compose
  Reflect
} derive(Debug, Eq, ToJson)

///|
pub(all) struct LessonStep {
  index : Int
  stage : LessonStage
  title : String
  prompt : String
  expected_rule : RuleKind?
} derive(Debug, Eq, ToJson)

///|
pub(all) struct PracticePlan {
  title : String
  objective : String
  steps : Array[LessonStep]
  estimated_minutes : Int
  difficulty : String
} derive(Debug, Eq, ToJson)

///|
pub(all) struct PracticeResult {
  completed : Int
  total : Int
  score : Int
  next_stage : LessonStage
  feedback : String
} derive(Debug, Eq, ToJson)

///|
pub fn lesson_stage_name(stage : LessonStage) -> String {
  match stage {
    Listen => "listen"
    Identify => "identify"
    Repair => "repair"
    Compose => "compose"
    Reflect => "reflect"
  }
}

///|
pub fn lesson_stage_index(stage : LessonStage) -> Int {
  match stage {
    Listen => 0
    Identify => 1
    Repair => 2
    Compose => 3
    Reflect => 4
  }
}

///|
pub fn next_lesson_stage(stage : LessonStage) -> LessonStage {
  match stage {
    Listen => Identify
    Identify => Repair
    Repair => Compose
    Compose => Reflect
    Reflect => Reflect
  }
}

///|
pub fn lesson_step(
  index : Int,
  stage : LessonStage,
  title : String,
  prompt : String,
  rule : RuleKind?,
) -> LessonStep {
  { index, stage, title, prompt, expected_rule: rule }
}

///|
pub fn default_practice_plan(rule : RuleKind) -> PracticePlan {
  let title = "Practice \{rule_family(rule)}"
  let steps = [
    lesson_step(
      0,
      Listen,
      "Hear the phrase",
      "Play or inspect the phrase before editing it.",
      None,
    ),
    lesson_step(
      1,
      Identify,
      "Locate the event",
      "Find the smallest event that demonstrates the rule.",
      Some(rule),
    ),
    lesson_step(
      2,
      Repair,
      "Try one repair",
      "Change one note and compare the new findings.",
      Some(rule),
    ),
    lesson_step(
      3,
      Compose,
      "Write a variation",
      "Create a second phrase using the same constraint.",
      Some(rule),
    ),
    lesson_step(
      4,
      Reflect,
      "Explain the choice",
      "Record why the final choice is more stable.",
      None,
    ),
  ]
  {
    title,
    objective: rule_explanation(rule),
    steps,
    estimated_minutes: 15 + lesson_stage_index(Reflect) * 2,
    difficulty: if rule_is_structural(rule) {
      "foundation"
    } else {
      "intermediate"
    },
  }
}

///|
pub fn plan_for_analysis(analysis : Analysis) -> PracticePlan {
  match highest_severity(analysis) {
    None => default_practice_plan(CadenceShape)
    Some(Error) =>
      if analysis_has_rule(analysis, ParallelPerfect) {
        default_practice_plan(ParallelPerfect)
      } else if analysis_has_rule(analysis, LargeLeap) {
        default_practice_plan(LargeLeap)
      } else {
        default_practice_plan(VoiceCrossing)
      }
    Some(Warning) => default_practice_plan(CadenceShape)
    Some(Info) => default_practice_plan(SpeciesRhythm)
  }
}

///|
pub fn evaluate_practice(
  plan : PracticePlan,
  completed : Int,
  score : Int,
) -> PracticeResult {
  let safe_completed = if completed < 0 {
    0
  } else if completed > plan.steps.length() {
    plan.steps.length()
  } else {
    completed
  }
  let next = if safe_completed == 0 {
    Listen
  } else {
    plan.steps[safe_completed - 1].stage
  }
  {
    completed: safe_completed,
    total: plan.steps.length(),
    score: if score < 0 {
      0
    } else if score > 100 {
      100
    } else {
      score
    },
    next_stage: next_lesson_stage(next),
    feedback: if safe_completed == plan.steps.length() {
      "Practice complete; write a short reflection."
    } else {
      "Continue with the next guided step."
    },
  }
}

///|
pub fn plan_progress(result : PracticeResult) -> Int {
  if result.total == 0 {
    0
  } else {
    result.completed * 100 / result.total
  }
}

///|
pub fn plan_is_complete(result : PracticeResult) -> Bool {
  result.completed == result.total
}

///|
pub fn plan_step(plan : PracticePlan, index : Int) -> LessonStep? {
  if index < 0 || index >= plan.steps.length() {
    None
  } else {
    Some(plan.steps[index])
  }
}

///|
pub fn plan_markdown(plan : PracticePlan) -> String {
  let lines : Array[String] = [
    "## \{plan.title}",
    "",
    "- Objective: \{plan.objective}",
    "- Difficulty: \{plan.difficulty}",
    "- Estimated time: \{plan.estimated_minutes} minutes",
    "",
  ]
  for step in plan.steps {
    lines.push(
      "\{step.index + 1}. [\{lesson_stage_name(step.stage)}] \{step.title} — \{step.prompt}",
    )
  }
  lines.join("\n")
}

///|
pub fn lesson_stage_order() -> Array[LessonStage] {
  [Listen, Identify, Repair, Compose, Reflect]
}

///|
pub fn lesson_prompts(stage : LessonStage) -> Array[String] {
  match stage {
    Listen =>
      [
        "What do you hear?", "Where does the phrase feel stable?", "Which event attracts attention?",
      ]
    Identify =>
      [
        "Which rule is involved?", "What is the interval?", "Is the beat accented?",
      ]
    Repair =>
      [
        "Can contrary motion help?", "Can the leap be reduced?", "Can the cadence be clarified?",
      ]
    Compose =>
      [
        "Write a two-note alternative.", "Extend the idea.", "Keep the profile consistent.",
      ]
    Reflect =>
      [
        "What changed?", "Which finding disappeared?", "What would you try next?",
      ]
  }
}

///|
pub fn rule_learning_level(rule : RuleKind) -> String {
  if rule_is_structural(rule) {
    "foundation"
  } else if rule_is_harmonic(rule) {
    "intermediate"
  } else {
    "advanced"
  }
}

///|
pub fn learning_objective(rule : RuleKind) -> String {
  "Learner can identify and repair \{rule_family(rule)} findings with an evidence-based edit."
}