///|
pub(all) enum TimelineEventKind {
  EventPhaseStart
  EventPhaseEnd
  EventManualApproval
  EventMetricCheckpoint
  EventRollbackGuard
} derive(Eq, Debug)

///|
pub(all) struct TimelineEvent {
  minute : Int
  phase_name : String
  kind : TimelineEventKind
  mode : RolloutMode
  percent_bp : Int
  note : String
} derive(Eq, Debug)

///|
pub fn TimelineEvent::TimelineEvent(
  minute? : Int = 0,
  phase_name? : String = "",
  kind? : TimelineEventKind = EventPhaseStart,
  mode? : RolloutMode = Shadow,
  percent_bp? : Int = 0,
  note? : String = "",
) -> TimelineEvent {
  { minute, phase_name, kind, mode, percent_bp, note }
}

///|
pub fn rollout_timeline(plan : RolloutPlan) -> Array[TimelineEvent] {
  let events = Array::new(capacity=plan.phases.length() * 4)
  for phase in plan.phases {
    events.push(
      TimelineEvent(
        minute=phase.start_minute,
        phase_name=phase.name,
        kind=EventPhaseStart,
        mode=phase.mode,
        percent_bp=phase.percent_bp,
        note="phase starts",
      ),
    )
    if phase.require_manual_approval {
      events.push(
        TimelineEvent(
          minute=phase.start_minute,
          phase_name=phase.name,
          kind=EventManualApproval,
          mode=phase.mode,
          percent_bp=phase.percent_bp,
          note="manual approval required",
        ),
      )
    }
    if phase.mode == Live || phase.mode == DryRun {
      events.push(
        TimelineEvent(
          minute=phase.start_minute + phase.duration_minutes / 2,
          phase_name=phase.name,
          kind=EventMetricCheckpoint,
          mode=phase.mode,
          percent_bp=phase.percent_bp,
          note="mid-phase metric checkpoint",
        ),
      )
    }
    if phase.mode == KillSwitch {
      events.push(
        TimelineEvent(
          minute=phase.start_minute,
          phase_name=phase.name,
          kind=EventRollbackGuard,
          mode=phase.mode,
          percent_bp=phase.percent_bp,
          note="rollback guard active",
        ),
      )
    }
    events.push(
      TimelineEvent(
        minute=phase.start_minute + phase.duration_minutes,
        phase_name=phase.name,
        kind=EventPhaseEnd,
        mode=phase.mode,
        percent_bp=phase.percent_bp,
        note="phase ends",
      ),
    )
  }
  events
}

///|
pub fn timeline_event_text(kind : TimelineEventKind) -> String {
  match kind {
    EventPhaseStart => "phase-start"
    EventPhaseEnd => "phase-end"
    EventManualApproval => "manual-approval"
    EventMetricCheckpoint => "metric-checkpoint"
    EventRollbackGuard => "rollback-guard"
  }
}

///|
pub fn next_timeline_event(
  events : Array[TimelineEvent],
  minute : Int,
) -> TimelineEvent? {
  let mut chosen : TimelineEvent? = None
  let mut chosen_minute = 2147483
  for event in events {
    if event.minute >= minute && event.minute < chosen_minute {
      chosen = Some(event)
      chosen_minute = event.minute
    }
  }
  chosen
}

///|
pub fn timeline_event_count_by_kind(
  events : Array[TimelineEvent],
  kind : TimelineEventKind,
) -> Int {
  let mut total = 0
  for event in events {
    if event.kind == kind {
      total += 1
    }
  }
  total
}

///|
pub fn phase_minutes_by_mode(plan : RolloutPlan, mode : RolloutMode) -> Int {
  let mut total = 0
  for phase in plan.phases {
    if phase.mode == mode {
      total += phase.duration_minutes
    }
  }
  total
}

///|
pub fn timeline_has_manual_gate(plan : RolloutPlan) -> Bool {
  for event in rollout_timeline(plan) {
    if event.kind == EventManualApproval {
      return true
    }
  }
  false
}

///|
pub fn support_handoff_minutes(
  plan : RolloutPlan,
  max_shift_minutes : Int,
) -> Array[Int] {
  let out = Array::new(capacity=plan.phases.length())
  let mut previous_end = 0
  let mut initialized = false
  for phase in plan.phases {
    if initialized {
      let gap = phase.start_minute - previous_end
      if gap >= max_shift_minutes {
        out.push(previous_end)
      }
    }
    previous_end = phase.start_minute + phase.duration_minutes
    initialized = true
  }
  out
}

///|
pub fn timeline_markdown(plan : RolloutPlan) -> String {
  let events = rollout_timeline(plan)
  let out = StringBuilder()
  out.write_string("# CanaryRing Timeline\n\n")
  out.write_string("- Plan: " + plan.name + "\n")
  out.write_string(
    "- Duration: " + plan_duration(plan).to_string() + " minutes\n",
  )
  out.write_string(
    "- Shadow minutes: " +
    phase_minutes_by_mode(plan, Shadow).to_string() +
    "\n",
  )
  out.write_string(
    "- Dry-run minutes: " +
    phase_minutes_by_mode(plan, DryRun).to_string() +
    "\n",
  )
  out.write_string(
    "- Live minutes: " + phase_minutes_by_mode(plan, Live).to_string() + "\n\n",
  )
  for event in events {
    out.write_string("- minute " + event.minute.to_string() + " ")
    out.write_string(timeline_event_text(event.kind) + " ")
    out.write_string(event.phase_name + " ")
    out.write_string(mode_text(event.mode) + " ")
    out.write_string(percent_text(event.percent_bp) + " ")
    out.write_string(event.note + "\n")
  }
  out.to_string()
}

///|
pub fn timeline_json(plan : RolloutPlan) -> String {
  let events = rollout_timeline(plan)
  let out = StringBuilder()
  out.write_string("{")
  out.write_string("\"plan\":\"" + json_escape(plan.name) + "\",")
  out.write_string(
    "\"duration_minutes\":" + plan_duration(plan).to_string() + ",",
  )
  out.write_string("\"events\":[")
  for i in 0.. 0 {
      out.write_string(",")
    }
    out.write_string("{")
    out.write_string("\"minute\":" + event.minute.to_string() + ",")
    out.write_string("\"phase\":\"" + json_escape(event.phase_name) + "\",")
    out.write_string("\"kind\":\"" + timeline_event_text(event.kind) + "\",")
    out.write_string("\"mode\":\"" + mode_text(event.mode) + "\",")
    out.write_string("\"percent_bp\":" + event.percent_bp.to_string())
    out.write_string("}")
  }
  out.write_string("]}")
  out.to_string()
}