///|
pub struct LineIndexEntry {
  line : Int
  start : Int
  end : Int
} derive(Eq, Debug, ToJson)

///|
pub struct MappingIndex {
  segments : Array[MappingSegment]
  lines : Array[LineIndexEntry]
} derive(Eq, Debug, ToJson)

///|
pub fn SourceMap::index(self : SourceMap) -> MappingIndex {
  let ordered = sorted_mapping_copy(self.mappings)
  let lines : Array[LineIndexEntry] = []
  let mut current_line = -1
  let mut start = 0
  for i, segment in ordered {
    if segment.generated_line != current_line {
      if current_line >= 0 {
        lines.push({ line: current_line, start, end: i })
      }
      current_line = segment.generated_line
      start = i
    }
  }
  if current_line >= 0 {
    lines.push({ line: current_line, start, end: ordered.length() })
  }
  { segments: ordered, lines }
}

///|
pub fn MappingIndex::line_count(self : MappingIndex) -> Int {
  self.lines.length()
}

///|
pub fn MappingIndex::segment_count(self : MappingIndex) -> Int {
  self.segments.length()
}

///|
pub fn MappingIndex::entry_for_line(
  self : MappingIndex,
  line : Int,
) -> LineIndexEntry? {
  for entry in self.lines {
    if entry.line == line {
      return Some(entry)
    }
  }
  None
}

///|
pub fn MappingIndex::segments_for_line(
  self : MappingIndex,
  line : Int,
) -> Array[MappingSegment] {
  match self.entry_for_line(line) {
    Some(entry) => self.segments[entry.start:entry.end].to_owned()
    None => []
  }
}

///|
pub fn MappingIndex::first_segment_for_line(
  self : MappingIndex,
  line : Int,
) -> MappingSegment? {
  match self.entry_for_line(line) {
    Some(entry) =>
      if entry.start < entry.end {
        Some(self.segments[entry.start])
      } else {
        None
      }
    None => None
  }
}

///|
pub fn MappingIndex::last_segment_for_line(
  self : MappingIndex,
  line : Int,
) -> MappingSegment? {
  match self.entry_for_line(line) {
    Some(entry) =>
      if entry.start < entry.end {
        Some(self.segments[entry.end - 1])
      } else {
        None
      }
    None => None
  }
}

///|
pub fn MappingIndex::find_generated_floor(
  self : MappingIndex,
  line : Int,
  column : Int,
) -> MappingSegment? {
  match self.entry_for_line(line) {
    Some(entry) => {
      let mut best : MappingSegment? = None
      for i in entry.start.. None
  }
}

///|
pub fn MappingIndex::find_generated_exact(
  self : MappingIndex,
  line : Int,
  column : Int,
) -> MappingSegment? {
  match self.entry_for_line(line) {
    Some(entry) => {
      for i in entry.start.. column {
          return None
        }
      }
      None
    }
    None => None
  }
}

///|
pub fn MappingIndex::line_has_mappings(self : MappingIndex, line : Int) -> Bool {
  self.entry_for_line(line) is Some(_)
}

///|
pub fn MappingIndex::mapped_segments(
  self : MappingIndex,
) -> Array[MappingSegment] {
  self.segments.filter(segment => segment.is_mapped())
}

///|
pub fn MappingIndex::unmapped_segments(
  self : MappingIndex,
) -> Array[MappingSegment] {
  self.segments.filter(segment => !segment.is_mapped())
}

///|
pub fn MappingIndex::named_segments(
  self : MappingIndex,
) -> Array[MappingSegment] {
  self.segments.filter(segment => segment.has_name())
}

///|
pub fn MappingIndex::generated_line_numbers(self : MappingIndex) -> Array[Int] {
  self.lines.map(entry => entry.line)
}

///|
pub fn MappingIndex::line_summary(
  self : MappingIndex,
  line : Int,
) -> LineSummary {
  let segments = self.segments_for_line(line)
  let mapped = segments.count_if(segment => segment.is_mapped())
  let unmapped = segments.length() - mapped
  let first = match segments.get(0) {
    Some(segment) => Some(segment.generated_column)
    None => None
  }
  let last = match segments.last() {
    Some(segment) => Some(segment.generated_column)
    None => None
  }
  {
    line,
    segment_count: segments.length(),
    mapped_segment_count: mapped,
    unmapped_segment_count: unmapped,
    first_column: first,
    last_column: last,
  }
}

///|
pub fn MappingIndex::summaries(self : MappingIndex) -> Array[LineSummary] {
  self.lines.map(entry => self.line_summary(entry.line))
}