///|
pub(all) enum RunbookRecordKind {
  OwnerNote
  MetricNote
  RollbackNote
  ApprovalNote
  IncidentNote
  ReleaseNote
  SupportNote
} derive(Eq, Debug)

///|
pub(all) enum RunbookState {
  Draft
  Ready
  Accepted
  NeedsFollowup
} derive(Eq, Debug)

///|
pub(all) struct RunbookRecord {
  id : String
  kind : RunbookRecordKind
  state : RunbookState
  title : String
  evidence : String
  owner : String
  minute : Int
  action : String
} derive(Eq, Debug)

///|
pub fn RunbookRecord::RunbookRecord(
  id? : String = "",
  kind? : RunbookRecordKind = OwnerNote,
  state? : RunbookState = Ready,
  title? : String = "",
  evidence? : String = "",
  owner? : String = "",
  minute? : Int = 0,
  action? : String = "",
) -> RunbookRecord {
  { id, kind, state, title, evidence, owner, minute, action }
}

///|
pub(all) struct RunbookReport {
  score : Int
  verdict : String
  records : Array[RunbookRecord]
  findings : Array[RolloutFinding]
  owner_count : Int
  metric_count : Int
  rollback_count : Int
  approval_count : Int
  release_count : Int
} derive(Eq, Debug)

///|
pub fn runbook_review(records : Array[RunbookRecord]) -> RunbookReport {
  let findings = Array::new(capacity=12)
  push_runbook_kind(
    findings,
    records,
    OwnerNote,
    "runbook.owner",
    "Owner evidence is retained",
  )
  push_runbook_kind(
    findings,
    records,
    MetricNote,
    "runbook.metrics",
    "Metric evidence is retained",
  )
  push_runbook_kind(
    findings,
    records,
    RollbackNote,
    "runbook.rollback",
    "Rollback evidence is retained",
  )
  push_runbook_kind(
    findings,
    records,
    ApprovalNote,
    "runbook.approval",
    "Approval evidence is retained",
  )
  push_runbook_kind(
    findings,
    records,
    ReleaseNote,
    "runbook.release",
    "Release evidence is retained",
  )
  push_runbook_evidence(findings, records)
  push_runbook_followup(findings, records)
  let score = runbook_score(findings)
  {
    score,
    verdict: runbook_verdict(findings, score),
    records,
    findings,
    owner_count: count_runbook_kind(records, OwnerNote),
    metric_count: count_runbook_kind(records, MetricNote),
    rollback_count: count_runbook_kind(records, RollbackNote),
    approval_count: count_runbook_kind(records, ApprovalNote),
    release_count: count_runbook_kind(records, ReleaseNote),
  }
}

///|
fn push_runbook_kind(
  findings : Array[RolloutFinding],
  records : Array[RunbookRecord],
  kind : RunbookRecordKind,
  id : String,
  title : String,
) -> Unit {
  let count = count_runbook_kind(records, kind)
  if count > 0 {
    findings.push(
      finding_pass(id, PlanShape, title, count.to_string() + " records"),
    )
  } else {
    findings.push(
      finding_warn(
        id,
        PlanShape,
        title + " missing",
        "0 records",
        "Add one " + runbook_kind_text(kind) + " record to the rollout runbook.",
        penalty=6,
      ),
    )
  }
}

///|
fn push_runbook_evidence(
  findings : Array[RolloutFinding],
  records : Array[RunbookRecord],
) -> Unit {
  let mut missing = 0
  for record in records {
    if record.state == Ready || record.state == Accepted {
      if !has_text(record.evidence) && !has_text(record.action) {
        missing += 1
      }
    }
  }
  if missing == 0 {
    findings.push(
      finding_pass(
        "runbook.evidence",
        PlanShape,
        "Ready records have evidence",
        "all ready records",
      ),
    )
  } else {
    findings.push(
      finding_warn(
        "runbook.evidence",
        PlanShape,
        "Some ready records lack evidence",
        missing.to_string() + " records",
        "Attach metric windows, release links or operator actions to ready records.",
        penalty=missing * 2,
      ),
    )
  }
}

///|
fn push_runbook_followup(
  findings : Array[RolloutFinding],
  records : Array[RunbookRecord],
) -> Unit {
  let mut followups = 0
  for record in records {
    if record.state == NeedsFollowup {
      followups += 1
    }
  }
  if followups == 0 {
    findings.push(
      finding_pass(
        "runbook.followup",
        PlanShape,
        "No unresolved follow-up records",
        "0 followups",
      ),
    )
  } else {
    findings.push(
      finding_warn(
        "runbook.followup",
        PlanShape,
        "Follow-up records remain",
        followups.to_string() + " records",
        "Make sure follow-ups describe future maintenance rather than unfinished launch requirements.",
        penalty=followups * 4,
      ),
    )
  }
}

///|
fn count_runbook_kind(
  records : Array[RunbookRecord],
  kind : RunbookRecordKind,
) -> Int {
  let mut total = 0
  for record in records {
    if record.kind == kind {
      total += 1
    }
  }
  total
}

///|
fn runbook_score(findings : Array[RolloutFinding]) -> Int {
  let mut score = 100
  for finding in findings {
    score -= finding.penalty
  }
  clamp(score, 0, 100)
}

///|
fn runbook_verdict(findings : Array[RolloutFinding], score : Int) -> String {
  let mut failures = 0
  let mut warnings = 0
  for finding in findings {
    if finding.severity == Fail {
      failures += 1
    } else if finding.severity == Warn {
      warnings += 1
    }
  }
  if failures > 0 {
    "blocked"
  } else if score >= 95 && warnings == 0 {
    "runbook-ready"
  } else if score >= 80 {
    "runbook-ready-with-notes"
  } else {
    "runbook-needs-work"
  }
}

///|
pub fn RunbookReport::to_markdown(self : RunbookReport) -> String {
  let out = StringBuilder()
  out.write_string("# CanaryRing Runbook Review\n\n")
  out.write_string("- Verdict: " + self.verdict + "\n")
  out.write_string("- Score: " + self.score.to_string() + "/100\n")
  out.write_string("- Owner records: " + self.owner_count.to_string() + "\n")
  out.write_string("- Metric records: " + self.metric_count.to_string() + "\n")
  out.write_string(
    "- Rollback records: " + self.rollback_count.to_string() + "\n",
  )
  out.write_string(
    "- Approval records: " + self.approval_count.to_string() + "\n",
  )
  out.write_string(
    "- Release records: " + self.release_count.to_string() + "\n\n",
  )
  out.write_string("## Findings\n\n")
  for finding in self.findings {
    out.write_string("- [" + severity_text(finding.severity) + "] ")
    out.write_string(finding.id + ": " + finding.title)
    if finding.evidence != "" {
      out.write_string(" -- " + finding.evidence)
    }
    out.write_string("\n")
  }
  out.write_string("\n## Records\n\n")
  for record in self.records {
    out.write_string("- " + record.id + " ")
    out.write_string("(" + runbook_kind_text(record.kind) + ", ")
    out.write_string(runbook_state_text(record.state) + "): ")
    out.write_string(record.title)
    if record.evidence != "" {
      out.write_string(" -- " + record.evidence)
    }
    out.write_string("\n")
  }
  out.to_string()
}

///|
fn runbook_kind_text(kind : RunbookRecordKind) -> String {
  match kind {
    OwnerNote => "owner"
    MetricNote => "metric"
    RollbackNote => "rollback"
    ApprovalNote => "approval"
    IncidentNote => "incident"
    ReleaseNote => "release"
    SupportNote => "support"
  }
}

///|
fn runbook_state_text(state : RunbookState) -> String {
  match state {
    Draft => "draft"
    Ready => "ready"
    Accepted => "accepted"
    NeedsFollowup => "needs-followup"
  }
}

///|
pub fn example_runbook_records() -> Array[RunbookRecord] {
  [
    RunbookRecord(
      id="rb-001",
      kind=OwnerNote,
      state=Accepted,
      title="Owner is EJJ-ai-nb",
      evidence="moon.mod and repository owner",
      owner="EJJ-ai-nb",
      minute=0,
    ),
    RunbookRecord(
      id="rb-002",
      kind=MetricNote,
      state=Accepted,
      title="Healthy metric windows retained",
      evidence="healthy_metrics fixture and CI output",
      owner="EJJ-ai-nb",
      minute=60,
    ),
    RunbookRecord(
      id="rb-003",
      kind=RollbackNote,
      state=Ready,
      title="Rollback decision uses gate failure",
      evidence="evaluate_gate returns Rollback on hard metric failure",
      owner="EJJ-ai-nb",
      minute=120,
    ),
    RunbookRecord(
      id="rb-004",
      kind=ApprovalNote,
      state=Ready,
      title="Manual approval required for 50% live phase",
      evidence="live-50 phase has require_manual_approval=true",
      owner="EJJ-ai-nb",
      minute=180,
    ),
    RunbookRecord(
      id="rb-005",
      kind=ReleaseNote,
      state=Ready,
      title="Release evidence points to canaryring",
      evidence="EJJ-ai-nb/canaryring",
      owner="EJJ-ai-nb",
      minute=240,
    ),
    RunbookRecord(
      id="rb-006",
      kind=SupportNote,
      state=Ready,
      title="Support handoff keeps rollout on hold if gate warns",
      evidence="GateDecision Hold blocks auto-advance",
      owner="EJJ-ai-nb",
      minute=240,
    ),
  ]
}