///|
/// What the oracle (`tools/gleam-ast-oracle`) writes for one file: the
/// parse result, or the compiler's first parse error. `kind` is the
/// `ParseErrorType` variant name; for `LexError`, `lexical_kind` is the
/// `LexicalErrorType` name as `@scanner.LexicalErrorType::kind()` gives it.
pub(all) enum OracleOutput {
  Parsed(Parsed)
  Error(kind~ : String, lexical_kind~ : String?, location~ : @span.SrcSpan)
} derive(Eq, Debug)

///|
/// Encode a parsed module or the compiler's first error.
/// Raise `ExportError` if the parsed module contains an extension target.
/// Use `canonical` to write the JSON text, then add a line feed for oracle output.
///
/// ```mbt check
/// test {
///   let value : @ast.OracleOutput = Error(
///     kind="UnexpectedEof",
///     lexical_kind=None,
///     location=@span.SrcSpan::from_ints(0, 0).unwrap(),
///   )
///   assert_eq(@ast.decode_oracle_output(@ast.encode_oracle_output(value)), value)
/// }
/// ```
pub fn encode_oracle_output(o : OracleOutput) -> Json raise ExportError {
  match o {
    Parsed(p) => encode_parsed(p)
    Error(kind~, lexical_kind~, location~) =>
      obj([
        (
          "error",
          obj([
            ("kind", str(kind)),
            ("lexical_kind", opt(lexical_kind, str)),
            ("location", encode_span(location)),
          ]),
        ),
      ])
  }
}

///|
/// Decode the oracle's parsed module or first-error object.
/// An `error` field selects the error form. Otherwise, decode a parsed module.
/// Raise `DecodeError` with the field path for malformed input.
pub fn decode_oracle_output(json : Json) -> OracleOutput raise DecodeError {
  let m = dec_object(json, Root)
  match m.get("error") {
    Some(e) => {
      let path = Key(Root, "error")
      Error(
        kind=dec_field(e, path, "kind", dec_string),
        lexical_kind=dec_field(e, path, "lexical_kind", (v, p) => {
          dec_nullable(v, p, dec_string)
        }),
        location=dec_field(e, path, "location", dec_span),
      )
    }
    None => Parsed(decode_parsed(json))
  }
}