///|
/// Interval analytics for calendars, reservations, and resource timelines.
///
/// Timeline records are half-open intervals `[start, end)`. Keeping this
/// convention explicit makes boundary-touching reservations composable and
/// lets capacity checks share the same logic as scheduling constraints.
pub struct TimelineInterval {
  id : Int
  resource : Int
  start : Int
  end : Int
  weight : Int
  label : String
}

///|
/// Create an interval.
pub fn timeline_interval(
  id : Int,
  resource : Int,
  start : Int,
  end : Int,
  weight : Int,
  label : String,
) -> TimelineInterval {
  {
    id,
    resource,
    start,
    end,
    weight: if weight < 0 {
      0
    } else {
      weight
    },
    label,
  }
}

///|
/// Return interval duration.
pub fn TimelineInterval::duration(self : TimelineInterval) -> Int {
  if self.end > self.start {
    self.end - self.start
  } else {
    0
  }
}

///|
/// Return whether an interval is valid.
pub fn TimelineInterval::valid(self : TimelineInterval) -> Bool {
  self.resource >= 0 && self.start <= self.end && self.weight >= 0
}

///|
/// Return whether a time point is covered.
pub fn TimelineInterval::contains(self : TimelineInterval, time : Int) -> Bool {
  time >= self.start && time < self.end
}

///|
/// A collection of resource intervals.
pub struct ResourceTimeline {
  horizon : Int
  capacities : Array[Int]
  intervals : Array[TimelineInterval]
}

///|
/// Create an empty timeline.
pub fn resource_timeline(
  horizon : Int,
  capacities : Array[Int],
) -> ResourceTimeline? {
  if horizon < 0 || capacities.length() == 0 {
    return None
  }
  for capacity in capacities {
    if capacity < 0 {
      return None
    }
  }
  Some({ horizon, capacities: capacities.copy(), intervals: [] })
}

///|
/// Add an interval after bounds validation.
pub fn ResourceTimeline::add(
  self : ResourceTimeline,
  interval : TimelineInterval,
) -> Bool {
  if !interval.valid() ||
    interval.resource >= self.capacities.length() ||
    interval.start < 0 ||
    interval.end > self.horizon ||
    self.contains_id(interval.id) {
    return false
  }
  self.intervals.push(interval)
  true
}

///|
/// Return whether an id exists.
pub fn ResourceTimeline::contains_id(self : ResourceTimeline, id : Int) -> Bool {
  for interval in self.intervals {
    if interval.id == id {
      return true
    }
  }
  false
}

///|
/// Return interval count.
pub fn ResourceTimeline::length(self : ResourceTimeline) -> Int {
  self.intervals.length()
}

///|
/// Return intervals on one resource.
pub fn ResourceTimeline::on_resource(
  self : ResourceTimeline,
  resource : Int,
) -> Array[TimelineInterval] {
  let result : Array[TimelineInterval] = []
  for interval in self.intervals {
    if interval.resource == resource {
      result.push(interval)
    }
  }
  result
}

///|
/// Return the load at one resource and time.
pub fn ResourceTimeline::load_at(
  self : ResourceTimeline,
  resource : Int,
  time : Int,
) -> Int {
  let mut result = 0
  for interval in self.intervals {
    if interval.resource == resource && interval.contains(time) {
      result += interval.weight
    }
  }
  result
}

///|
/// Return a resource load profile.
pub fn ResourceTimeline::profile(
  self : ResourceTimeline,
  resource : Int,
) -> Array[Int] {
  let result : Array[Int] = []
  for time in 0.. Array[Int] {
  let result : Array[Int] = []
  if resource < 0 || resource >= self.capacities.length() {
    return result
  }
  for time, load in self.profile(resource) {
    if load > self.capacities[resource] {
      result.push(time)
    }
  }
  result
}

///|
/// Return whether all capacity rules hold.
pub fn ResourceTimeline::feasible(self : ResourceTimeline) -> Bool {
  for resource in 0.. 0 {
      return false
    }
  }
  true
}

///|
/// Return interval overlap.
pub fn timeline_overlap(
  left : TimelineInterval,
  right : TimelineInterval,
) -> Bool {
  left.resource == right.resource &&
  left.start < right.end &&
  right.start < left.end
}

///|
/// Return all conflicting interval id pairs.
pub fn ResourceTimeline::conflicts(
  self : ResourceTimeline,
) -> Array[(Int, Int)] {
  let result : Array[(Int, Int)] = []
  for left in 0.. Array[(Int, Int)] {
  let result : Array[(Int, Int)] = []
  if resource < 0 || resource >= self.capacities.length() {
    return result
  }
  let profile = self.profile(resource)
  let mut open = -1
  for time, load in profile {
    if load == 0 && open < 0 {
      open = time
    } else if load > 0 && open >= 0 {
      result.push((open, time))
      open = -1
    }
  }
  if open >= 0 {
    result.push((open, self.horizon))
  }
  result
}

///|
/// Return the earliest free slot of a requested duration.
pub fn ResourceTimeline::find_slot(
  self : ResourceTimeline,
  resource : Int,
  duration : Int,
) -> Int? {
  if duration < 0 || resource < 0 || resource >= self.capacities.length() {
    return None
  }
  for slot in self.free_slots(resource) {
    if slot.1 - slot.0 >= duration {
      return Some(slot.0)
    }
  }
  None
}

///|
/// Return weighted utilization in integer percentage points.
pub fn ResourceTimeline::utilization(
  self : ResourceTimeline,
  resource : Int,
) -> Int {
  if resource < 0 ||
    resource >= self.capacities.length() ||
    self.horizon == 0 ||
    self.capacities[resource] == 0 {
    return 0
  }
  integer_sum(self.profile(resource)) *
  100 /
  (self.horizon * self.capacities[resource])
}

///|
/// Return the latest occupied time.
pub fn ResourceTimeline::makespan(self : ResourceTimeline) -> Int {
  let mut result = 0
  for interval in self.intervals {
    if interval.end > result {
      result = interval.end
    }
  }
  result
}

///|
/// Return total work.
pub fn ResourceTimeline::total_work(self : ResourceTimeline) -> Int {
  let mut result = 0
  for interval in self.intervals {
    result += interval.duration() * interval.weight
  }
  result
}

///|
/// Return intervals sorted by start time.
pub fn ResourceTimeline::ordered(
  self : ResourceTimeline,
  resource : Int,
) -> Array[TimelineInterval] {
  let result = self.on_resource(resource)
  for left in 0.. Array[TimelineInterval] {
  let ordered = intervals.copy()
  for left in 0.. 0 {
      let previous = result[result.length() - 1]
      if previous.resource == interval.resource &&
        previous.label == interval.label &&
        previous.end >= interval.start {
        result[result.length() - 1] = {
          ..previous,
          end: if previous.end > interval.end {
            previous.end
          } else {
            interval.end
          },
        }
        continue
      }
    }
    result.push(interval)
  }
  result
}

///|
/// Return the total covered duration after merging intervals.
pub fn covered_duration(intervals : Array[TimelineInterval]) -> Int {
  let merged = merge_timeline_intervals(intervals)
  let mut result = 0
  for interval in merged {
    result += interval.duration()
  }
  result
}

///|
/// Shift all intervals by an offset.
pub fn shift_timeline(
  intervals : Array[TimelineInterval],
  offset : Int,
) -> Array[TimelineInterval] {
  intervals.map(interval => {
    ..interval,
    start: interval.start + offset,
    end: interval.end + offset,
  })
}

///|
/// Return intervals that contain a time point.
pub fn intervals_at(
  intervals : Array[TimelineInterval],
  time : Int,
) -> Array[Int] {
  let result : Array[Int] = []
  for interval in intervals {
    if interval.contains(time) {
      result.push(interval.id)
    }
  }
  result
}

///|
/// Return a stable timeline fingerprint.
pub fn ResourceTimeline::signature(self : ResourceTimeline) -> Int {
  let mut result = self.horizon * 31
  for interval in self.intervals {
    result = result * 37 +
      interval.id * 3 +
      interval.resource * 5 +
      interval.start * 7 +
      interval.end * 11 +
      interval.weight
  }
  result
}

///|
/// Render resource intervals.
pub fn ResourceTimeline::render(self : ResourceTimeline) -> String {
  let builder = StringBuilder()
  for resource in 0.. 0 {
      builder.write_char('\n')
    }
    builder.write_string("resource \{resource}:")
    for interval in self.ordered(resource) {
      builder.write_string(
        " \{interval.label}@\{interval.start}-\{interval.end}",
      )
    }
  }
  builder.to_string()
}

///|
/// Return the number of active resources at a time.
pub fn active_resource_count(timeline : ResourceTimeline, time : Int) -> Int {
  let result : Array[Int] = []
  for interval in timeline.intervals {
    if interval.contains(time) && !result.contains(interval.resource) {
      result.push(interval.resource)
    }
  }
  result.length()
}

///|
/// Return a timeline quality score.
pub fn timeline_objective(timeline : ResourceTimeline) -> Int {
  timeline.makespan() +
  timeline.conflicts().length() * 1000000 +
  timeline.overloads(0).length() * 1000
}