///|
type Parser

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

///|
#borrow(self)
extern "c" fn Parser::is_null(self : Parser) -> Bool = "moonbit_c_is_null"

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

///|
#borrow(self)
extern "c" fn ts_parser_set_language(self : Parser, language : Language) = "moonbit_ts_parser_set_language"

///|
/// 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 {
  ts_parser_set_language(self, language)
}

///|
/// 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(self, ranges)
extern "c" fn ts_parser_set_included_ranges(
  self : 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]
    flatten_ranges[i * 6 + 1] = range._[1]
    flatten_ranges[i * 6 + 2] = range._[2]
    flatten_ranges[i * 6 + 3] = range._[3]
    flatten_ranges[i * 6 + 4] = range._[4]
    flatten_ranges[i * 6 + 5] = range._[5]
  }
  return ts_parser_set_included_ranges(self, flatten_ranges)
}

///|
#borrow(self)
extern "c" fn ts_parser_included_ranges(self : 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) {
    Range::_new(
      Point::_new(flatten_ranges[i * 6], flatten_ranges[i * 6 + 1]),
      Point::_new(flatten_ranges[i * 6 + 2], flatten_ranges[i * 6 + 3]),
      flatten_ranges[i * 6 + 4],
      flatten_ranges[i * 6 + 5],
    )
  })
}

///|
// @move input
#borrow(self, old_tree)
extern "c" fn ts_parser_parse(
  self : Parser,
  old_tree : Tree,
  input : (UInt, Point, FixedArray[UInt]) -> Bytes,
  encoding : InputEncoding,
  decode : @c.Nullable[
    FuncRef[(@c.Pointer[Byte], UInt, @c.Pointer[Int]) -> Int],
  ]
) -> Tree = "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, }
}

///|
// @move input
// @move progress_callback
#borrow(self, old_tree)
extern "c" fn ts_parser_parse_with_options(
  self : Parser,
  old_tree : Tree,
  input : (UInt, Point, FixedArray[UInt]) -> Bytes,
  encoding : UInt,
  decode : @c.Nullable[
    FuncRef[(@c.Pointer[Byte], UInt, @c.Pointer[Int]) -> Int],
  ],
  progress_callback : (UInt, Bool) -> Bool
) -> Tree = "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 Parser::parse[Encoding : DecodeFunction](
  self : Parser,
  old_tree : Tree?,
  input : Input[Encoding],
  options? : ParseOptions
) -> Tree? {
  let encoding = input.decode.encoding()
  let decode : @c.Nullable[
    FuncRef[(@c.Pointer[Byte], UInt, @c.Pointer[Int]) -> Int],
  ] = match encoding {
    Custom =>
      @c.Nullable::some(fn(bytes, length, code_point) {
        let buffer = @buffer.new()
        for i in 0.. {
            code_point.store(result.code_point)
            result.bytes_read
          }
          None => -1
        }
      })
    _ => @c.Nullable::none()
  }
  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)
    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
  }
  guard options is Some(options) else {
    ts_parser_parse(self, old_tree.or(ts_tree_null()), read, encoding, decode).to_option()
  }
  ts_parser_parse_with_options(
    self,
    old_tree.or(ts_tree_null()),
    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()
}

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

///|
#borrow(self, old_tree, string)
extern "c" fn ts_parser_parse_bytes_encoding(
  self : Parser,
  old_tree : Tree,
  string : Bytes,
  encoding : UInt
) -> Tree = "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? : @encoding.Encoding
) -> Tree? {
  match encoding {
    None =>
      ts_parser_parse_bytes(self, old_tree.or(ts_tree_null()), bytes).to_option()
    Some(encoding) =>
      ts_parser_parse_bytes_encoding(
        self,
        old_tree.or(ts_tree_null()),
        bytes,
        encoding.encoding().to_uint(),
      ).to_option()
  }
}

///|
/// 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 : String
) -> Tree? {
  ts_parser_parse_bytes(
    self,
    old_tree.or(ts_tree_null()),
    @encoding.encode(UTF8, string),
  ).to_option()
}

///|
#borrow(self)
extern "c" fn ts_parser_reset(self : 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::to_uint(self : LogType) -> UInt {
  match self {
    Parse => 0
    Lex => 1
  }
}

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

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

///|
fn Logger::new(log : (LogType, String) -> Unit) -> Logger {
  fn(log_type, message) {
    log(
      LogType::of_uint(log_type),
      @encoding.decoder(UTF8).decode_lossy(message),
    )
  }
}

///|
// @move logger
#borrow(self)
extern "c" fn ts_parser_set_logger(self : 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(self)
extern "c" fn ts_parser_logger(self : Parser) -> Logger = "moonbit_ts_parser_logger"

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