///|
let wasm_source_mapping_url_name = "sourceMappingURL"

///|
priv struct WasmSourceMappingSection {
  start : Int
  end : Int
  url_start : Int
  url_end : Int
}

///|
fn decode_wasm_u32(
  input : Bytes,
  start : Int,
  limit : Int,
) -> (Int, Int) raise SourceMapError {
  let mut offset = start
  let mut value = 0
  let mut shift = 0
  for count = 0; count < 5; count = count + 1 {
    if offset >= limit {
      raise SourceMapError::At(start, "truncated Wasm unsigned LEB128")
    }
    let byte = input[offset].to_int()
    if count == 4 && (byte & 0xf0) != 0 {
      raise SourceMapError::At(start, "Wasm unsigned LEB128 exceeds u32")
    }
    if count == 4 && (byte & 0x08) != 0 {
      raise SourceMapError::At(
        start, "Wasm unsigned LEB128 exceeds supported Int range",
      )
    }
    value = value | ((byte & 0x7f) << shift)
    offset += 1
    if (byte & 0x80) == 0 {
      return (value, offset)
    }
    shift += 7
  }
  raise SourceMapError::At(start, "Wasm unsigned LEB128 exceeds u32")
}

///|
fn require_wasm_header(input : Bytes) -> Unit raise SourceMapError {
  if input.length() < 8 {
    raise SourceMapError::At(0, "truncated WebAssembly header")
  }
  let expected : Array[Byte] = [
    b'\x00', b'\x61', b'\x73', b'\x6d', b'\x01', b'\x00', b'\x00', b'\x00',
  ]
  for i in 0.. WasmSourceMappingSection? raise SourceMapError {
  require_wasm_header(input)
  let mut offset = 8
  let mut found : WasmSourceMappingSection? = None
  while offset < input.length() {
    let section_start = offset
    let section_id = input[offset].to_int()
    offset += 1
    let (payload_size, payload_start) = decode_wasm_u32(
      input,
      offset,
      input.length(),
    )
    if payload_size > input.length() - payload_start {
      raise SourceMapError::At(
        section_start, "Wasm section exceeds input length",
      )
    }
    let section_end = payload_start + payload_size
    if section_id == 0 {
      let (name_size, name_start) = decode_wasm_u32(
        input, payload_start, section_end,
      )
      if name_size > section_end - name_start {
        raise SourceMapError::At(
          payload_start, "Wasm custom section name exceeds payload",
        )
      }
      let name_end = name_start + name_size
      let name = @utf8.decode(input.view(start=name_start, end=name_end)) catch {
        _ =>
          raise SourceMapError::At(
            name_start, "Wasm custom section name is not UTF-8",
          )
      }
      if name == wasm_source_mapping_url_name {
        if found is Some(_) {
          raise SourceMapError::At(
            section_start, "duplicate sourceMappingURL custom section",
          )
        }
        let (url_size, url_start) = decode_wasm_u32(
          input, name_end, section_end,
        )
        if url_size > section_end - url_start {
          raise SourceMapError::At(name_end, "sourceMappingURL exceeds payload")
        }
        let url_end = url_start + url_size
        if url_end != section_end {
          raise SourceMapError::At(
            url_end, "sourceMappingURL has trailing bytes",
          )
        }
        found = Some({
          start: section_start,
          end: section_end,
          url_start,
          url_end,
        })
      }
    }
    offset = section_end
  }
  found
}

///|
fn append_wasm_bytes(
  output : Array[Byte],
  input : Bytes,
  start : Int,
  end : Int,
) -> Unit {
  for i in start.. Unit {
  let mut remaining = value
  for ;; {
    let mut byte = remaining & 0x7f
    remaining = remaining >> 7
    if remaining != 0 {
      byte = byte | 0x80
    }
    output.push(byte.to_byte())
    if remaining == 0 {
      break
    }
  }
}

///|
fn make_source_mapping_section(url : String) -> Bytes {
  let name = @utf8.encode(wasm_source_mapping_url_name)
  let encoded_url = @utf8.encode(url)
  let payload : Array[Byte] = []
  append_wasm_u32(payload, name.length())
  append_wasm_bytes(payload, name, 0, name.length())
  append_wasm_u32(payload, encoded_url.length())
  append_wasm_bytes(payload, encoded_url, 0, encoded_url.length())
  let section : Array[Byte] = [b'\x00']
  append_wasm_u32(section, payload.length())
  for byte in payload {
    section.push(byte)
  }
  Bytes::from_array(section)
}

///|
/// Read the URL stored in a WebAssembly `sourceMappingURL` custom section.
pub fn wasm_source_mapping_url(wasm : Bytes) -> String? raise SourceMapError {
  match find_wasm_source_mapping_section(wasm) {
    None => None
    Some(section) =>
      Some(
        @utf8.decode(wasm.view(start=section.url_start, end=section.url_end)) catch {
          _ =>
            raise SourceMapError::At(
              section.url_start,
              "sourceMappingURL payload is not UTF-8",
            )
        },
      )
  }
}

///|
/// Add or replace a WebAssembly `sourceMappingURL` custom section.
pub fn wasm_set_source_mapping_url(
  wasm : Bytes,
  url : String,
) -> Bytes raise SourceMapError {
  let existing = find_wasm_source_mapping_section(wasm)
  let replacement = make_source_mapping_section(url)
  let output : Array[Byte] = []
  match existing {
    None => {
      append_wasm_bytes(output, wasm, 0, wasm.length())
      append_wasm_bytes(output, replacement, 0, replacement.length())
    }
    Some(section) => {
      append_wasm_bytes(output, wasm, 0, section.start)
      append_wasm_bytes(output, replacement, 0, replacement.length())
      append_wasm_bytes(output, wasm, section.end, wasm.length())
    }
  }
  Bytes::from_array(output)
}

///|
/// Remove a WebAssembly `sourceMappingURL` custom section when present.
pub fn wasm_remove_source_mapping_url(
  wasm : Bytes,
) -> Bytes raise SourceMapError {
  match find_wasm_source_mapping_section(wasm) {
    None => wasm
    Some(section) => {
      let output : Array[Byte] = []
      append_wasm_bytes(output, wasm, 0, section.start)
      append_wasm_bytes(output, wasm, section.end, wasm.length())
      Bytes::from_array(output)
    }
  }
}