///|
type Parser

///|
extern "c" fn ts_parser_new() -> Parser = "moonbit_ts_parser_new"

///|
/// Create a new parser.
pub fn Parser::new() -> Parser {
  ts_parser_new()
}

///|
pub fn parser(language : Language) -> Parser raise LanguageError {
  let parser = Parser::new()
  parser.set_language(language)
  return parser
}

///|
#borrow(parser)
extern "c" fn ts_parser_language(parser : Parser) -> Language = "moonbit_ts_parser_language"

///|
#borrow(language)
extern "c" fn ts_language_is_null(language : Language) -> Bool = "moonbit_c_is_null"

///|
/// Get the parser's current language.
pub fn Parser::language(self : Parser) -> Language? {
  let language = ts_parser_language(self)
  if ts_language_is_null(language) {
    None
  } else {
    Some(language)
  }
}

///|
#borrow(parser)
extern "c" fn ts_parser_set_language(
  parser : Parser,
  language : Language,
) -> Bool = "moonbit_ts_parser_set_language"

///|
suberror LanguageError {
  VersionMismatch(
    language_abi_version~ : Int,
    library_version~ : Int,
    min_compatible_version~ : Int
  )
} derive(Show)

///|
pub impl ToJson for LanguageError with to_json(self) -> Json {
  match self {
    VersionMismatch(
      language_abi_version~,
      library_version~,
      min_compatible_version~
    ) =>
      {
        "language_abi_version": language_abi_version.to_json(),
        "library_version": library_version.to_json(),
        "min_compatible_version": min_compatible_version.to_json(),
      }
  }
}

///|
/// Set the language that the parser should use for parsing.
///
/// Returns a boolean indicating whether or not the language was successfully
/// assigned. True means assignment succeeded. False means there was a version
/// mismatch: the language was generated with an incompatible version of the
/// Tree-sitter CLI. Check the language's ABI version using
/// `Language::abi_version` and compare it to this library's `LANGUAGE_VERSION`
/// and `MIN_COMPATIBLE_LANGUAGE_VERSION` constants.
pub fn Parser::set_language(
  self : Parser,
  language : Language,
) -> Unit raise LanguageError {
  let succeed = ts_parser_set_language(self, language)
  if not(succeed) {
    raise VersionMismatch(
      language_abi_version=language.abi_version(),
      library_version=LANGUAGE_VERSION,
      min_compatible_version=MIN_COMPATIBLE_LANGUAGE_VERSION,
    )
  }
}

///|
/// The latest ABI version that is supported by the current version of the
/// library. When Languages are generated by the Tree-sitter CLI, they are
/// assigned an ABI version number that corresponds to the current CLI version.
/// The Tree-sitter library is generally backwards-compatible with languages
/// generated using older CLI versions, but is not forwards-compatible.
pub const LANGUAGE_VERSION = 15

///|
///
/// The earliest ABI version that is supported by the current version of the
/// library.
pub const MIN_COMPATIBLE_LANGUAGE_VERSION = 13

///|
#borrow(parser, ranges)
extern "c" fn ts_parser_set_included_ranges(
  parser : Parser,
  ranges : FixedArray[UInt],
) -> Bool = "moonbit_ts_parser_set_included_ranges"

///|
/// Set the ranges of text that the parser should include when parsing.
///
/// By default, the parser will always include entire documents. This function
/// allows you to parse only a *portion* of a document but still return a syntax
/// tree whose ranges match up with the document as a whole. You can also pass
/// multiple disjoint ranges.
///
/// The second and third parameters specify the location and length of an array
/// of ranges.
///
/// If `count` is zero, then the entire document will be parsed. Otherwise,
/// the given ranges must be ordered from earliest to latest in the document,
/// and they must not overlap. That is, the following must hold for all:
///
/// `i < count - 1`: `ranges[i].end_byte <= ranges[i + 1].start_byte`
///
/// If this requirement is not satisfied, the operation will fail, the ranges
/// will not be assigned, and this function will return `false`. On success,
/// this function returns `true`
pub fn Parser::set_included_ranges(
  self : Parser,
  ranges : Array[Range],
) -> Bool {
  let flatten_ranges = FixedArray::make(ranges.length() * 6, 0U)
  for i, range in ranges {
    flatten_ranges[i * 6] = range.0[0]
    flatten_ranges[i * 6 + 1] = range.0[1]
    flatten_ranges[i * 6 + 2] = range.0[2]
    flatten_ranges[i * 6 + 3] = range.0[3]
    flatten_ranges[i * 6 + 4] = range.0[4]
    flatten_ranges[i * 6 + 5] = range.0[5]
  }
  return ts_parser_set_included_ranges(self, flatten_ranges)
}

///|
#borrow(parser)
extern "c" fn ts_parser_included_ranges(parser : Parser) -> FixedArray[UInt] = "moonbit_ts_parser_included_ranges"

///|
/// Get the ranges of text that the parser will include when parsing.
pub fn Parser::included_ranges(self : Parser) -> Array[Range] {
  let flatten_ranges = ts_parser_included_ranges(self)
  Array::makei(flatten_ranges.length() / 6, fn(i) {
    ts_range_new(
      ts_point_new(flatten_ranges[i * 6], flatten_ranges[i * 6 + 1]),
      ts_point_new(flatten_ranges[i * 6 + 2], flatten_ranges[i * 6 + 3]),
      flatten_ranges[i * 6 + 4],
      flatten_ranges[i * 6 + 5],
    )
  })
}

///|
#borrow(parser, old_tree)
extern "c" fn ts_parser_parse(
  parser : Parser,
  old_tree : TSTree,
  input : (UInt, Point, FixedArray[UInt]) -> Bytes,
  encoding : InputEncoding,
  decode : FuncRef[(@c.Pointer[Byte], UInt, @c.Pointer[Int]) -> Int],
) -> TSTree = "moonbit_ts_parser_parse"

///|
/// The state of a parse operation.
pub struct ParseState {
  current_byte_offset : Int
  has_error : Bool
}

///|
/// Options for parsing.
struct ParseOptions {
  progress_callback : (ParseState) -> Bool
}

///|
/// Create new parse options with the given progress callback.
pub fn ParseOptions::new(
  progress_callback : (ParseState) -> Bool,
) -> ParseOptions {
  ParseOptions::{ progress_callback, }
}

///|
#borrow(parser, old_tree)
extern "c" fn ts_parser_parse_with_options(
  parser : Parser,
  old_tree : TSTree,
  input : (UInt, Point, FixedArray[UInt]) -> Bytes,
  encoding : UInt,
  decode : FuncRef[(@c.Pointer[Byte], UInt, @c.Pointer[Int]) -> Int],
  progress_callback : (UInt, Bool) -> Bool,
) -> TSTree = "moonbit_ts_parser_parse_with_options"

///|
/// Use the parser to parse some source code and create a syntax tree.
///
/// If you are parsing this document for the first time, pass `None` for the
/// `old_tree` parameter. Otherwise, if you have already parsed an earlier
/// version of this document and the document has since been edited, pass the
/// previous syntax tree so that the unchanged parts of it can be reused.
/// This will save time and memory. For this to work correctly, you must have
/// already edited the old syntax tree using the `Tree::edit` function in a
/// way that exactly matches the source code changes.
///
/// The `Input` parameter lets you specify how to read the text. It has the
/// following fields:
/// 1. `read`: A function to retrieve a chunk of text at a given byte offset
///    and (row, column) position. The function should return a pointer to the
///    text and write its length to the `bytes_read` pointer. The parser does
///    not take ownership of this buffer; it just borrows it until it has
///    finished reading it. The function should write a zero value to the
///    `bytes_read` pointer to indicate the end of the document.
/// 2. `decode`: A function to decode the text. This is only used if the
///    encoding is `Custom`.
/// Additionally, you can pass `InputEncoding::UTF8` or `InputEncoding::UTF16`
/// to the `Input` parameter to specify the encoding of the text.
///
/// This function returns a syntax tree on success, and `None` on failure. There
/// are four possible reasons for failure:
/// 1. The parser does not have a language assigned. Check for this using the
///    `Parser::language` function.
/// 2. Parsing was cancelled due to a timeout that was set by an earlier call to
///    the `Parser::set_timeout_micros` function. You can resume parsing from
///    where the parser left out by calling `Parser::parse` again with the
///    same arguments. Or you can start parsing from scratch by first calling
///    `Parser::reset`.
/// 3. Parsing was cancelled using a cancellation flag that was set by an
///    earlier call to `Parser::set_cancellation_flag`. You can resume parsing
///    from where the parser left out by calling `Parser::parse` again with
///    the same arguments.
/// 4. Parsing was cancelled due to the progress callback returning true. This callback
///    is passed as the `options` argument inside the `ParseOptions` struct.
pub fn[Encoding : DecodeFunction] Parser::parse(
  self : Parser,
  old_tree? : Tree,
  input : Input[Encoding],
  options? : ParseOptions,
) -> Tree raise ParseError {
  let encoding = input.decode.encoding()
  let decode : FuncRef[(@c.Pointer[Byte], UInt, @c.Pointer[Int]) -> Int] = match encoding {
    Custom =>
      fn(bytes, length, code_point) {
        let buffer = @buffer.new()
        for i in 0.. {
            code_point.store(result.code_point.to_int())
            result.bytes_read
          }
          None => -1
        }
      }
    _ => fn(_bytes, _length, _code_point) { 0 }
  }
  let mut stage : BytesView = []
  let text = StringBuilder::new()
  let mut chunk = None
  let read : (UInt, Point, FixedArray[UInt]) -> Bytes = fn(
    offset : UInt,
    point : Point,
    range : FixedArray[UInt],
  ) {
    let bytes_view = (input.read)(offset, point)
    stage = [..stage, ..bytes_view]
    loop Encoding::decode(stage) {
      Some(result) => {
        text.write_char(result.code_point)
        stage = stage[result.bytes_read:]
        if stage.length() == 0 {
          break
        } else {
          continue Encoding::decode(stage)
        }
      }
      None => ()
    }
    let bytes_start = bytes_view.start_offset()
    let bytes_length = bytes_view.length()
    range[0] = int_to_uint(bytes_start)
    range[1] = int_to_uint(bytes_length)
    let bytes_data = bytes_view.data()
    if chunk is Some(chunk) && physical_equal(chunk, bytes_data) {
      return bytes_data
    }
    if bytes_length != 0 {
      chunk = Some(bytes_data)
    } else {
      chunk = None
    }
    return bytes_data
  }
  let old_ts_tree = match old_tree {
    None => ts_tree_null()
    Some(tree) => tree.tree
  }
  guard options is Some(options) else {
    let tree = ts_parser_parse(self, old_ts_tree, read, encoding, decode).to_option()
    { tree: self.raise_parse_error(tree), text: text.to_string() }
  }
  let tree = ts_parser_parse_with_options(
    self,
    old_ts_tree,
    read,
    encoding.to_uint(),
    decode,
    fn(current_byte_offset, has_error) {
      let current_byte_offset = uint_to_int(current_byte_offset)
      (options.progress_callback)({ current_byte_offset, has_error })
    },
  ).to_option()
  { tree: self.raise_parse_error(tree), text: text.to_string() }
}

///|
#borrow(parser, old_tree, string)
extern "c" fn ts_parser_parse_bytes(
  parser : Parser,
  old_tree : TSTree,
  string : Bytes,
) -> TSTree = "moonbit_ts_parser_parse_string"

///|
#borrow(parser, old_tree, string)
extern "c" fn ts_parser_parse_bytes_encoding(
  parser : Parser,
  old_tree : TSTree,
  string : Bytes,
  encoding : UInt,
) -> TSTree = "moonbit_ts_parser_parse_string_encoding"

///|
/// Use the parser to parse some source code stored in one contiguous buffer
/// with a given encoding. The first three parameters work the same as in the
/// `parse` method. The final parameter indicates whether the text is encoded as
/// UTF8 or UTF16.
pub fn Parser::parse_bytes(
  self : Parser,
  old_tree? : Tree,
  bytes : Bytes,
  encoding~ : InputEncoding,
) -> Tree raise ParseError {
  let old_ts_tree = match old_tree {
    None => ts_tree_null()
    Some(tree) => tree.tree
  }
  let tree = ts_parser_parse_bytes_encoding(
    self,
    old_ts_tree,
    bytes,
    encoding.to_uint(),
  ).to_option()
  let text : StringView = match encoding {
    UTF8 => @utf8.decode_lossy(bytes)
    UTF16LE => @utf16.decode_lossy(bytes, endianness=Little)
    UTF16BE => @utf16.decode_lossy(bytes, endianness=Big)
    Custom => @utf8.decode_lossy(bytes)
  }
  { tree: self.raise_parse_error(tree), text }
}

///|
pub suberror ParseError {
  MissingLanguage
  Cancelled
} derive(Show)

///|
fn Parser::raise_parse_error(
  self : Parser,
  tree : TSTree?,
) -> TSTree raise ParseError {
  match tree {
    None =>
      match self.language() {
        None => raise MissingLanguage
        Some(_) => raise Cancelled
      }
    Some(tree) => return tree
  }
}

///|
/// Use the parser to parse some source code stored in one contiguous string buffer.
/// The first two parameters are the same as in the `parse` function. The final
/// parameter is the string to parse.
pub fn Parser::parse_string(
  self : Parser,
  old_tree? : Tree,
  string : StringView,
) -> Tree raise ParseError {
  let old_ts_tree = match old_tree {
    None => ts_tree_null()
    Some(tree) => tree.tree
  }
  let tree = ts_parser_parse_bytes(self, old_ts_tree, @utf8.encode(string)).to_option()
  { tree: self.raise_parse_error(tree), text: string }
}

///|
#borrow(parser)
extern "c" fn ts_parser_reset(parser : Parser) = "moonbit_ts_parser_reset"

///|
/// Instruct the parser to start the next parse from the beginning.
///
/// If the parser previously failed because of a timeout or a cancellation, then
/// by default, it will resume where it left off on the next call to
/// `parse` or other parsing functions. If you don't want to resume,
/// and instead intend to use this parser to parse some other document, you must
/// call `reset` first.
pub fn Parser::reset(self : Parser) -> Unit {
  ts_parser_reset(self)
}

///|
pub enum LogType {
  Parse
  Lex
}

///|
fn LogType::of_uint(value : UInt) -> LogType {
  match value {
    0 => Parse
    1 => Lex
    value => abort("Invalid log type: \{value}")
  }
}

///|
struct Logger((UInt, Bytes) -> Unit)

///|
pub fn Logger::new(log : (LogType, StringView) -> Unit) -> Logger {
  fn(log_type, message) {
    log(LogType::of_uint(log_type), @utf8.decode_lossy(message))
  }
}

///|
#borrow(parser)
extern "c" fn ts_parser_set_logger(parser : Parser, logger : Logger) = "moonbit_ts_parser_set_logger"

///|
/// Set the logger that a parser should use during parsing.
///
/// The parser does not take ownership over the logger payload. If a logger was
/// previously assigned, the caller is responsible for releasing any memory
/// owned by the previous logger.
pub fn Parser::set_logger(self : Parser, logger : Logger) -> Unit {
  ts_parser_set_logger(self, logger)
}

///|
#borrow(parser)
extern "c" fn ts_parser_logger(parser : Parser) -> Logger = "moonbit_ts_parser_logger"

///|
/// Get the parser's current logger.
pub fn Parser::logger(self : Parser) -> Logger {
  ts_parser_logger(self)
}