///|
pub struct VlqTraceStep {
  offset : Int
  char : String
  digit : Int
  payload : Int
  continuation : Bool
  shift : Int
  partial : Int
} derive(Eq, Debug, ToJson)

///|
pub struct SegmentTrace {
  line : Int
  segment : Int
  start_offset : Int
  end_offset : Int
  raw : String
  values : Array[Int]
  decoded : MappingSegment?
  error : String?
} derive(Eq, Debug, ToJson)

///|
pub fn trace_vlq(
  input : StringView,
  offset : Int,
) -> Result[Array[VlqTraceStep], SourceMapError] {
  let steps : Array[VlqTraceStep] = []
  let mut result = 0
  let mut shift = 0
  let mut index = offset
  for ;; {
    if index >= input.length() {
      return Err(TruncatedVlq(offset~))
    }
    let ch = input.unsafe_get(index).unsafe_to_char()
    let digit = match base64_value(ch) {
      Some(value) => value
      None => return Err(InvalidBase64(char=ch, offset=index))
    }
    let continuation = (digit & vlq_continuation_bit) != 0
    let payload = digit & vlq_base_mask
    if shift >= 30 && payload > 1 {
      return Err(VlqOverflow(offset=index))
    }
    result = result | (payload << shift)
    steps.push({
      offset: index,
      char: ch.to_string(),
      digit,
      payload,
      continuation,
      shift,
      partial: result,
    })
    index += 1
    shift += vlq_base_shift
    if !continuation {
      break
    }
  }
  Ok(steps)
}

///|
fn segment_raw(input : StringView, start : Int, end : Int) -> String {
  input[start:end].to_owned()
}

///|
pub fn trace_mappings(input : StringView) -> Array[SegmentTrace] {
  let traces : Array[SegmentTrace] = []
  let mut generated_line = 0
  let mut generated_column = 0
  let mut source_index = 0
  let mut original_line = 0
  let mut original_column = 0
  let mut name_index = 0
  let mut index = 0
  let mut segment_index = 0
  let mut line_has_segment = false
  while index < input.length() {
    let ch = input.unsafe_get(index).unsafe_to_char()
    if ch == ';' {
      generated_line += 1
      generated_column = 0
      segment_index = 0
      line_has_segment = false
      index += 1
    } else if ch == ',' {
      if !line_has_segment {
        traces.push({
          line: generated_line,
          segment: segment_index,
          start_offset: index,
          end_offset: index + 1,
          raw: ",",
          values: [],
          decoded: None,
          error: Some("empty segment"),
        })
      }
      segment_index += 1
      index += 1
    } else {
      let start = index
      match decode_segment_values(input, index) {
        Ok((values, next)) => {
          let raw = segment_raw(input, start, next)
          if values.length() == 1 {
            generated_column += values[0]
            let decoded = MappingSegment::unmapped(
              generated_line, generated_column,
            )
            traces.push({
              line: generated_line,
              segment: segment_index,
              start_offset: start,
              end_offset: next,
              raw,
              values,
              decoded: Some(decoded),
              error: None,
            })
          } else if values.length() == 4 || values.length() == 5 {
            generated_column += values[0]
            source_index += values[1]
            original_line += values[2]
            original_column += values[3]
            let decoded = if values.length() == 5 {
              name_index += values[4]
              MappingSegment::mapped(
                generated_line,
                generated_column,
                source_index,
                original_line,
                original_column,
                name_index~,
              )
            } else {
              MappingSegment::mapped(
                generated_line, generated_column, source_index, original_line, original_column,
              )
            }
            traces.push({
              line: generated_line,
              segment: segment_index,
              start_offset: start,
              end_offset: next,
              raw,
              values,
              decoded: Some(decoded),
              error: None,
            })
          } else {
            traces.push({
              line: generated_line,
              segment: segment_index,
              start_offset: start,
              end_offset: next,
              raw,
              values,
              decoded: None,
              error: Some("segment must contain 1, 4 or 5 VLQ fields"),
            })
          }
          line_has_segment = true
          index = next
        }
        Err(err) => {
          traces.push({
            line: generated_line,
            segment: segment_index,
            start_offset: start,
            end_offset: input.length(),
            raw: input[start:].to_owned(),
            values: [],
            decoded: None,
            error: Some(err.message()),
          })
          break
        }
      }
    }
  }
  traces
}

///|
pub fn SegmentTrace::summary(self : SegmentTrace) -> String {
  let values = self.values.map(value => value.to_string()).join(",")
  match self.error {
    Some(error) =>
      "line=\{self.line} segment=\{self.segment} raw=\{self.raw} values=[\{values}] error=\{error}"
    None =>
      "line=\{self.line} segment=\{self.segment} raw=\{self.raw} values=[\{values}] ok"
  }
}

///|
pub fn trace_mappings_report(input : StringView) -> String {
  let buf = StringBuilder()
  for trace in trace_mappings(input) {
    buf.write_string(trace.summary())
    buf.write_char('\n')
  }
  buf.to_string()
}