///|
fn literal_error(
  code : String,
  message : String,
  span : SourceSpan,
) -> ParseError {
  ParseError(code~, message~, span~)
}

///|
fn hex_value(value : UInt16) -> Int? {
  match value {
    '0'..='9' => Some(value.to_int() - '0')
    'a'..='f' => Some(value.to_int() - 'a' + 10)
    'A'..='F' => Some(value.to_int() - 'A' + 10)
    _ => None
  }
}

///|
fn decode_hex(
  content : String,
  start : Int,
  digits : Int,
  span : SourceSpan,
) -> Int raise ParseError {
  if start + digits > content.length() {
    raise literal_error(
      "CEL-LIT-ESCAPE",
      "hex escape is shorter than the required \{digits} digits",
      span,
    )
  }
  let mut offset = 0
  let mut value = 0
  while offset < digits {
    match hex_value(content[start + offset]) {
      Some(digit) => {
        value = value * 16 + digit
        offset += 1
      }
      None =>
        raise literal_error(
          "CEL-LIT-ESCAPE", "hex escape contains a non-hexadecimal digit", span,
        )
    }
  }
  value
}

///|
fn codepoint_utf8(codepoint : Int, span : SourceSpan) -> Bytes raise ParseError {
  if codepoint < 0 ||
    codepoint > 0x10ffff ||
    (codepoint >= 0xd800 && codepoint <= 0xdfff) {
    raise literal_error(
      "CEL-LIT-UNICODE", "escape does not encode a Unicode scalar value", span,
    )
  }
  let bytes : Array[Byte] = []
  if codepoint <= 0x7f {
    bytes.push(codepoint.to_byte())
  } else if codepoint <= 0x7ff {
    bytes.push((0xc0 | (codepoint >> 6)).to_byte())
    bytes.push((0x80 | (codepoint & 0x3f)).to_byte())
  } else if codepoint <= 0xffff {
    bytes.push((0xe0 | (codepoint >> 12)).to_byte())
    bytes.push((0x80 | ((codepoint >> 6) & 0x3f)).to_byte())
    bytes.push((0x80 | (codepoint & 0x3f)).to_byte())
  } else {
    bytes.push((0xf0 | (codepoint >> 18)).to_byte())
    bytes.push((0x80 | ((codepoint >> 12) & 0x3f)).to_byte())
    bytes.push((0x80 | ((codepoint >> 6) & 0x3f)).to_byte())
    bytes.push((0x80 | (codepoint & 0x3f)).to_byte())
  }
  Bytes::from_array(bytes)
}

///|
fn append_codepoint(
  builder : StringBuilder,
  codepoint : Int,
  span : SourceSpan,
) -> Unit raise ParseError {
  let encoded = codepoint_utf8(codepoint, span)
  let decoded = @utf8.decode(encoded) catch {
    _ =>
      raise literal_error(
        "CEL-LIT-UNICODE", "failed to decode Unicode escape", span,
      )
  }
  builder.write_string(decoded)
}

///|
fn append_source_char(
  builder : StringBuilder,
  content : String,
  index : Int,
) -> Int {
  match content.get_char(index) {
    Some(character) => {
      builder.write_char(character)
      character.utf16_len()
    }
    None => 1
  }
}

///|
fn decode_quoted(
  content : String,
  raw : Bool,
  span : SourceSpan,
) -> String raise ParseError {
  if raw {
    return content
  }
  let builder = StringBuilder(size_hint=content.length())
  let mut index = 0
  while index < content.length() {
    if content[index] != '\\' {
      index += append_source_char(builder, content, index)
      continue
    }
    let escape_start = index
    index += 1
    if index >= content.length() {
      raise literal_error(
        "CEL-LIT-ESCAPE", "quoted literal ends with an incomplete escape", span,
      )
    }
    let escape = content[index]
    index += 1
    match escape {
      'a' => append_codepoint(builder, 7, span)
      'b' => builder.write_char('\b')
      'f' => builder.write_char('\f')
      'n' => builder.write_char('\n')
      'r' => builder.write_char('\r')
      't' => builder.write_char('\t')
      'v' => append_codepoint(builder, 11, span)
      '\\' => builder.write_char('\\')
      '\'' => builder.write_char('\'')
      '"' => builder.write_char('"')
      '?' => builder.write_char('?')
      'x' | 'X' => {
        let value = decode_hex(content, index, 2, span)
        index += 2
        append_codepoint(builder, value, span)
      }
      'u' => {
        let high = decode_hex(content, index, 4, span)
        index += 4
        if high >= 0xd800 && high <= 0xdbff {
          if index + 6 > content.length() ||
            content[index] != '\\' ||
            content[index + 1] != 'u' {
            raise literal_error(
              "CEL-LIT-UNICODE", "leading surrogate must be followed by a trailing surrogate escape",
              span,
            )
          }
          let low = decode_hex(content, index + 2, 4, span)
          if low < 0xdc00 || low > 0xdfff {
            raise literal_error(
              "CEL-LIT-UNICODE", "leading surrogate is followed by an invalid trailing surrogate",
              span,
            )
          }
          index += 6
          append_codepoint(
            builder,
            0x10000 + ((high - 0xd800) << 10) + low - 0xdc00,
            span,
          )
        } else {
          append_codepoint(builder, high, span)
        }
      }
      'U' => {
        let value = decode_hex(content, index, 8, span)
        index += 8
        append_codepoint(builder, value, span)
      }
      '0'..='7' => {
        let mut value = escape.to_int() - '0'
        let mut consumed = 1
        while consumed < 3 &&
              index < content.length() &&
              content[index] >= '0' &&
              content[index] <= '7' {
          value = value * 8 + content[index].to_int() - '0'
          index += 1
          consumed += 1
        }
        if value > 255 {
          raise literal_error(
            "CEL-LIT-ESCAPE", "octal escape exceeds one byte", span,
          )
        }
        append_codepoint(builder, value, span)
      }
      _ =>
        raise literal_error(
          "CEL-LIT-ESCAPE",
          "unsupported escape at source offset \{span.start + escape_start}",
          span,
        )
    }
  }
  builder.to_string()
}

///|
fn parse_int_literal(
  text : String,
  span : SourceSpan,
) -> Literal raise ParseError {
  let value : Int64 = @string.parse_int64(text) catch {
    _ =>
      raise literal_error(
        "CEL-LIT-INT", "integer literal is outside the Int64 range", span,
      )
  }
  Literal::Int(value)
}

///|
fn parse_uint_literal(
  text : String,
  span : SourceSpan,
) -> Literal raise ParseError {
  let body = text[0:text.length() - 1]
  let value : UInt64 = @string.parse_uint64(body) catch {
    _ =>
      raise literal_error(
        "CEL-LIT-UINT", "unsigned integer literal is outside the UInt64 range", span,
      )
  }
  Literal::UInt(value)
}

///|
fn parse_double_literal(
  text : String,
  span : SourceSpan,
) -> Literal raise ParseError {
  let value : Double = @string.parse_double(text) catch {
    _ =>
      raise literal_error(
        "CEL-LIT-DOUBLE", "invalid floating-point literal", span,
      )
  }
  Literal::Double(value)
}

///|
fn literal_from_token(token : Token) -> Literal raise ParseError {
  match token.kind {
    NullLiteral => Literal::Null
    BoolLiteral(value) => Literal::Bool(value)
    IntLiteral(text) => parse_int_literal(text, token.span)
    UIntLiteral(text) => parse_uint_literal(text, token.span)
    DoubleLiteral(text) => parse_double_literal(text, token.span)
    StringLiteral(content, raw) =>
      Literal::String(decode_quoted(content, raw, token.span))
    BytesLiteral(content, raw) => {
      let decoded = decode_quoted(content, raw, token.span)
      Literal::Bytes(@utf8.encode(decoded))
    }
    _ =>
      raise literal_error(
        "CEL-LIT-EXPECTED",
        "expected one CEL literal, found \{token.label()}",
        token.span,
      )
  }
}

///|
/// Parse exactly one CEL literal without parsing a complete expression.
pub fn parse_literal(source : StringView) -> Literal raise ParseError {
  let tokens = lex(source.to_owned())
  if tokens.length() != 2 || tokens[1].kind != Eof {
    let span = if tokens.length() > 1 {
      tokens[1].span
    } else {
      SourceSpan::empty()
    }
    raise literal_error(
      "CEL-LIT-TRAILING", "literal input contains trailing tokens", span,
    )
  }
  literal_from_token(tokens[0])
}