///|
/// Categories used when turning reviewer feedback into a resubmission plan.
pub(all) enum ReviewIssueKind {
  DuplicateScope
  MissingExpansionRelation
  MissingEvidence
  DeadlineWindow
  ContactPath
  GeneralFeedback
} derive(Eq, @debug.Debug)

///|
/// Status for a remediation action.
pub(all) enum ActionStatus {
  Planned
  Done
  NotNeeded
} derive(Eq, @debug.Debug)

///|
/// One line-level issue extracted from review feedback.
pub(all) struct ReviewIssue {
  line : Int
  kind : ReviewIssueKind
  text : String
  evidence : String
} derive(Eq, @debug.Debug)

///|
/// A concrete difference between this project and a related package.
pub(all) struct Differentiator {
  axis : String
  this_project : String
  related_project : String
  reviewer_value : String
} derive(Eq, @debug.Debug)

///|
/// One action that can be attached to a resubmission.
pub(all) struct RemediationStep {
  id : String
  title : String
  status : ActionStatus
  proof : String
} derive(Eq, @debug.Debug)

///|
/// Full resubmission plan built from feedback and differentiators.
pub(all) struct ReviewPlan {
  title : String
  issues : Array[ReviewIssue]
  differentiators : Array[Differentiator]
  steps : Array[RemediationStep]
  overlap_addressed : Bool
  ready_to_resubmit : Bool
} derive(@debug.Debug)

///|
/// Stable label for reports.
pub fn ReviewIssueKind::label(self : ReviewIssueKind) -> String {
  match self {
    DuplicateScope => "duplicate-scope"
    MissingExpansionRelation => "missing-expansion-relation"
    MissingEvidence => "missing-evidence"
    DeadlineWindow => "deadline-window"
    ContactPath => "contact-path"
    GeneralFeedback => "general-feedback"
  }
}

///|
/// Stable label for remediation status.
pub fn ActionStatus::label(self : ActionStatus) -> String {
  match self {
    Planned => "planned"
    Done => "done"
    NotNeeded => "not-needed"
  }
}

///|
/// Build a differentiator record without exposing struct layout to callers.
pub fn Differentiator::make(
  axis : StringView,
  this_project : StringView,
  related_project : StringView,
  reviewer_value : StringView,
) -> Differentiator {
  {
    axis: axis.to_owned(),
    this_project: this_project.to_owned(),
    related_project: related_project.to_owned(),
    reviewer_value: reviewer_value.to_owned(),
  }
}

///|
/// Parse rejection or review feedback into line-level issues.
pub fn parse_review_feedback(feedback : StringView) -> Array[ReviewIssue] {
  let source = feedback.to_owned()
  let lines = review_split_lines(source)
  let issues : Array[ReviewIssue] = []
  for i in 0.. ReviewPlan {
  let issues = parse_review_feedback(feedback)
  let diffs = differentiators.to_owned()
  let overlap_found = has_issue(issues, DuplicateScope) ||
    has_issue(issues, MissingExpansionRelation)
  let overlap_addressed = !overlap_found || !diffs.is_empty()
  let steps = build_steps(issues, diffs)
  {
    title: title.to_owned(),
    issues,
    differentiators: diffs,
    steps,
    overlap_addressed,
    ready_to_resubmit: overlap_addressed && !steps.is_empty(),
  }
}

///|
/// Return true when the plan contains a parsed issue of the requested kind.
pub fn ReviewPlan::has_issue(self : ReviewPlan, kind : ReviewIssueKind) -> Bool {
  has_issue(self.issues, kind)
}

///|
/// Render a concise Markdown plan for attaching to an updated application.
pub fn ReviewPlan::to_markdown(self : ReviewPlan) -> String {
  let buf = StringBuilder()
  buf.write_string("# Constraint Lens Resubmission Plan\n\n")
  buf.write_string("- Target: \{self.title}\n")
  buf.write_string(
    "- Ready: \{if self.ready_to_resubmit { "yes" } else { "no" }}\n",
  )
  buf.write_string(
    "- Overlap addressed: \{if self.overlap_addressed { "yes" } else { "no" }}\n\n",
  )
  buf.write_string("## Parsed Review Issues\n\n")
  for issue in self.issues {
    buf.write_string(
      "- line \{issue.line} [\{issue.kind.label()}] \{review_clip(issue.text, 96)}\n",
    )
  }
  buf.write_string("\n## Differentiation Matrix\n\n")
  for diff in self.differentiators {
    buf.write_string("- Axis: \{diff.axis}\n")
    buf.write_string("  - Constraint Lens: \{diff.this_project}\n")
    buf.write_string("  - Related project: \{diff.related_project}\n")
    buf.write_string("  - Reviewer value: \{diff.reviewer_value}\n")
  }
  buf.write_string("\n## Remediation Steps\n\n")
  for step in self.steps {
    buf.write_string(
      "- [\{step.status.label()}] \{step.id}: \{step.title}; proof: \{step.proof}\n",
    )
  }
  buf.to_string()
}

///|
/// Built-in sample based on a duplicate-scope rejection scenario.
pub fn sample_rejection_feedback() -> String {
  (
    #|Your MoonBit hackathon application was not approved.
    #|The project scope overlaps with a recent README example and provenance proof package.
    #|Please explain the extension relationship and provide evidence before 2026-08-24.
  )
}

///|
/// Built-in novelty matrix for the runnable example.
pub fn sample_differentiators() -> Array[Differentiator] {
  [
    Differentiator::make(
      "primary artifact", "review feedback -> issue ledger -> resubmission plan",
      "README examples -> source provenance proof", "Shows why the revised project is not a Mooncakes checklist clone.",
    ),
    Differentiator::make(
      "runtime decision", "classifies rejection reasons and gates resubmission readiness",
      "classifies code snippets, licenses, and package snapshots", "Turns reviewer feedback into concrete repair work.",
    ),
    Differentiator::make(
      "non-goal", "does not extract README code blocks or prove third-party source origin",
      "does not manage rejection response planning", "Keeps the two packages composable instead of competing.",
    ),
  ]
}

///|
fn make_review_issue(
  line : Int,
  kind : ReviewIssueKind,
  text : String,
  evidence : String,
) -> ReviewIssue {
  { line, kind, text: review_clip(text, 160), evidence }
}

///|
fn build_steps(
  issues : Array[ReviewIssue],
  differentiators : Array[Differentiator],
) -> Array[RemediationStep] {
  let steps : Array[RemediationStep] = []
  if has_issue(issues, DuplicateScope) {
    steps.push({
      id: "differentiate-scope",
      title: "Document a novelty matrix against the related package",
      status: if differentiators.is_empty() {
        Planned
      } else {
        Done
      },
      proof: if differentiators.is_empty() {
        "missing differentiators"
      } else {
        "\{differentiators.length()} differentiator(s)"
      },
    })
  }
  if has_issue(issues, MissingExpansionRelation) {
    steps.push({
      id: "explain-relation",
      title: "State whether this is replacement, extension, or adjacent tooling",
      status: if differentiators.is_empty() {
        Planned
      } else {
        Done
      },
      proof: "README and differentiation notes",
    })
  }
  if has_issue(issues, MissingEvidence) {
    steps.push({
      id: "attach-evidence",
      title: "Attach build, test, CI, publish, and Mooncakes evidence",
      status: Done,
      proof: "release record and test record",
    })
  }
  if has_issue(issues, DeadlineWindow) {
    steps.push({
      id: "resubmit-before-deadline",
      title: "Prepare the updated application before the stated deadline",
      status: Done,
      proof: "resubmission brief",
    })
  }
  if steps.is_empty() {
    steps.push({
      id: "no-blocker",
      title: "No blocker detected in the supplied feedback",
      status: NotNeeded,
      proof: "parser returned no blocking scope issue",
    })
  }
  steps
}

///|
fn has_issue(issues : Array[ReviewIssue], kind : ReviewIssueKind) -> Bool {
  for issue in issues {
    if issue.kind == kind {
      return true
    }
  }
  false
}

///|
fn review_split_lines(text : String) -> Array[String] {
  let lines : Array[String] = []
  if text.is_empty() {
    return lines
  }
  for view in text.split("\n") {
    lines.push(view.to_owned().trim_end(chars="\r").to_owned())
  }
  lines
}

///|
fn review_clip(input : String, max : Int) -> String {
  let trimmed = input.trim()
  if max <= 0 || trimmed.char_length() <= max {
    return trimmed.to_owned()
  }
  let buf = StringBuilder()
  let mut count = 0
  for ch in trimmed {
    if count < max {
      buf.write_char(ch)
      count += 1
    }
  }
  buf.write_string("...")
  buf.to_string()
}