///|
// ============================================================================
// Native backend: materialize `NativeChunk` into canonical `VectorChunk`
// (issue #61). Borrowed `duckdb_vector` handles are only used transiently
// inside `vector_from_native` — every `Vector` holds owned MoonBit arrays.
// ============================================================================

///|

///|
#borrow(chunk, col)
extern "C" fn native_chunk_vector(
  chunk : NativeChunk,
  col : Int,
) -> NativeVector = "duckdb_mb_chunk_vector"

///|

///|
#borrow(chunk, col)
extern "C" fn native_chunk_column_type(chunk : NativeChunk, col : Int) -> Int = "duckdb_mb_chunk_column_type"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_validity_flags(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[Int] = "duckdb_mb_vector_validity_flags"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_bool(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[Int] = "duckdb_mb_vector_bool"

///|

///|
#borrow(vector, type_id, rows)
extern "C" fn native_vector_i32(
  vector : NativeVector,
  type_id : Int,
  rows : Int,
) -> FixedArray[Int] = "duckdb_mb_vector_i32"

///|

///|
#borrow(vector, type_id, rows)
extern "C" fn native_vector_i64(
  vector : NativeVector,
  type_id : Int,
  rows : Int,
) -> FixedArray[Int64] = "duckdb_mb_vector_i64"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_u64(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[UInt64] = "duckdb_mb_vector_u64"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_f32(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[Float] = "duckdb_mb_vector_f32"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_f64(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[Double] = "duckdb_mb_vector_f64"

///|

///|
#borrow(vector, type_id, rows)
extern "C" fn native_vector_strings(
  vector : NativeVector,
  type_id : Int,
  rows : Int,
) -> FixedArray[Bytes] = "duckdb_mb_vector_strings"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_blobs(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[Bytes] = "duckdb_mb_vector_blobs"

///|

///|
#borrow(vector, type_id, rows)
extern "C" fn native_vector_i128_lo(
  vector : NativeVector,
  type_id : Int,
  rows : Int,
) -> FixedArray[UInt64] = "duckdb_mb_vector_i128_lo"

///|

///|
#borrow(vector, type_id, rows)
extern "C" fn native_vector_i128_hi(
  vector : NativeVector,
  type_id : Int,
  rows : Int,
) -> FixedArray[Int64] = "duckdb_mb_vector_i128_hi"

///|

///|
#borrow(vector)
extern "C" fn native_vector_decimal_meta(vector : NativeVector) -> Int = "duckdb_mb_vector_decimal_meta"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_decimal_lo(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[UInt64] = "duckdb_mb_vector_decimal_lo"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_decimal_hi(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[Int64] = "duckdb_mb_vector_decimal_hi"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_interval(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[Int64] = "duckdb_mb_vector_interval"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_list_offsets(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[UInt64] = "duckdb_mb_vector_list_offsets"

///|

///|
#borrow(vector, rows)
extern "C" fn native_vector_list_lengths(
  vector : NativeVector,
  rows : Int,
) -> FixedArray[UInt64] = "duckdb_mb_vector_list_lengths"

///|

///|
#borrow(vector)
extern "C" fn native_vector_list_child(vector : NativeVector) -> NativeVector = "duckdb_mb_vector_list_child"

///|

///|
#borrow(vector)
extern "C" fn native_vector_list_child_size(vector : NativeVector) -> Int64 = "duckdb_mb_vector_list_child_size"

///|

///|
#borrow(vector)
extern "C" fn native_vector_list_child_type(vector : NativeVector) -> Int = "duckdb_mb_vector_list_child_type"

///|

///|
#borrow(vector)
extern "C" fn native_vector_map_key_type(vector : NativeVector) -> Int = "duckdb_mb_vector_map_key_type"

///|

///|
#borrow(vector)
extern "C" fn native_vector_map_value_type(vector : NativeVector) -> Int = "duckdb_mb_vector_map_value_type"

///|

///|
#borrow(vector)
extern "C" fn native_vector_struct_child_count(vector : NativeVector) -> Int = "duckdb_mb_vector_struct_child_count"

///|

///|
#borrow(vector, child)
extern "C" fn native_vector_struct_child(
  vector : NativeVector,
  child : Int,
) -> NativeVector = "duckdb_mb_vector_struct_child"

///|

///|
#borrow(vector, child)
extern "C" fn native_vector_struct_child_name(
  vector : NativeVector,
  child : Int,
) -> Bytes = "duckdb_mb_vector_struct_child_name"

///|

///|
#borrow(vector, child)
extern "C" fn native_vector_struct_child_type(
  vector : NativeVector,
  child : Int,
) -> Int = "duckdb_mb_vector_struct_child_type"

///|

///|
#borrow(vector)
extern "C" fn native_vector_array_size(vector : NativeVector) -> Int = "duckdb_mb_vector_array_size"

///|

///|
#borrow(vector)
extern "C" fn native_vector_array_child(vector : NativeVector) -> NativeVector = "duckdb_mb_vector_array_child"

///|

///|
#borrow(vector)
extern "C" fn native_vector_array_child_type(vector : NativeVector) -> Int = "duckdb_mb_vector_array_child_type"

///|

///|
#borrow(result)
extern "C" fn native_result_fetch_chunk(result : NativeResult) -> NativeChunk = "duckdb_mb_result_fetch_chunk"

///|

///|
#borrow(chunk, col)
extern "C" fn native_chunk_col_strings(
  chunk : NativeChunk,
  col : Int,
) -> FixedArray[Bytes] = "duckdb_mb_chunk_col_strings"

///|

///|
#borrow(vector, type_id, row)
extern "C" fn native_vector_cell_string(
  vector : NativeVector,
  type_id : Int,
  row : Int,
) -> Bytes = "duckdb_mb_vector_cell_string"

// ============================================================================
// Vector materialization
// ============================================================================

///|
/// Decode one borrowed `NativeVector` into an owned `Vector`.
/// `type_id` is the column's `duckdb_type` id; `rows` the element count.
fn vector_from_native(
  vector : NativeVector,
  type_id : Int,
  rows : Int,
) -> Vector {
  let validity = validity_from_flags(
    native_vector_validity_flags(vector, rows),
    rows,
  )
  let logical_type = column_type_from_id(type_id)
  let data = vector_data_from_native(vector, type_id, rows)
  { logical_type, data, validity, }
}

///|
fn vector_data_from_native(
  vector : NativeVector,
  type_id : Int,
  rows : Int,
) -> VectorData {
  match type_id {
    1 =>
      VectorData::Bool(
        FixedArray::makei(rows, fn(i) {
          native_vector_bool(vector, rows)[i] == 1
        }),
      )
    2 | 3 | 4 | 6 | 7 | 13 =>
      // TINYINT/SMALLINT/INTEGER/UTINYINT/USMALLINT/DATE
      VectorData::Int32(native_vector_i32(vector, type_id, rows))
    5 | 8 | 14 | 39 | 12 | 20 | 21 | 22 | 31 =>
      // BIGINT/UINTEGER/TIME/TIME_NS/TIMESTAMP*/TIMESTAMP_TZ
      VectorData::Int64(native_vector_i64(vector, type_id, rows))
    9 => VectorData::UInt64(native_vector_u64(vector, rows))
    10 => VectorData::Float(native_vector_f32(vector, rows))
    11 => VectorData::Double(native_vector_f64(vector, rows))
    17 | 23 => {
      // VARCHAR / ENUM
      let raw = native_vector_strings(vector, type_id, rows)
      VectorData::Varchar(Array::makei(rows, fn(i) { bytes_to_string(raw[i]) }))
    }
    18 => {
      // BLOB
      let raw = native_vector_blobs(vector, rows)
      VectorData::Blob(Array::makei(rows, fn(i) { raw[i] }))
    }
    16 | 32 | 27 =>
      // HUGEINT / UHUGEINT / UUID
      VectorData::HugeInt(
        lower=native_vector_i128_lo(vector, type_id, rows),
        upper=native_vector_i128_hi(vector, type_id, rows),
      )
    19 => {
      // DECIMAL: metadata packed as (width << 16) | scale
      let meta = native_vector_decimal_meta(vector)
      let lo = native_vector_decimal_lo(vector, rows)
      let hi = native_vector_decimal_hi(vector, rows)
      VectorData::Decimal(
        FixedArray::makei(rows, fn(i) {
          {
            width: meta >> 16,
            scale: meta & 0xffff,
            lower: lo[i],
            upper: hi[i],
          }
        }),
      )
    }
    15 => {
      // INTERVAL: flattened [months, days, micros] triples
      let flat = native_vector_interval(vector, rows)
      VectorData::Interval(
        FixedArray::makei(rows, fn(i) {
          {
            months: flat[i * 3].to_int(),
            days: flat[i * 3 + 1].to_int(),
            micros: flat[i * 3 + 2],
          }
        }),
      )
    }
    24 => {
      // LIST
      let offsets = native_vector_list_offsets(vector, rows)
      let lengths = native_vector_list_lengths(vector, rows)
      let child_size = native_vector_list_child_size(vector).to_int()
      let child_type = native_vector_list_child_type(vector)
      let child_vector = native_vector_list_child(vector)
      let child = vector_from_native(child_vector, child_type, child_size)
      VectorData::List(child~, offsets~, lengths~)
    }
    25 => {
      // STRUCT
      let count = native_vector_struct_child_count(vector)
      let fields : Array[String] = []
      let children : Array[Vector] = []
      for i in 0.. {
      // MAP is a LIST of STRUCT{key, value} in the C API.
      let offsets = native_vector_list_offsets(vector, rows)
      let lengths = native_vector_list_lengths(vector, rows)
      let child_size = native_vector_list_child_size(vector).to_int()
      let child_vector = native_vector_list_child(vector)
      let keys = vector_from_native(
        native_vector_struct_child(child_vector, 0),
        native_vector_map_key_type(vector),
        child_size,
      )
      let values = vector_from_native(
        native_vector_struct_child(child_vector, 1),
        native_vector_map_value_type(vector),
        child_size,
      )
      VectorData::Map(offsets~, lengths~, keys~, values~)
    }
    33 => {
      // ARRAY: fixed `array_size` children per row, offsets are implicit.
      let array_size = native_vector_array_size(vector)
      let child_size = rows * array_size
      let child_type = native_vector_array_child_type(vector)
      let child = vector_from_native(
        native_vector_array_child(vector),
        child_type,
        child_size,
      )
      let offsets = FixedArray::makei(rows, fn(i) {
        (i * array_size).to_uint64()
      })
      let lengths = FixedArray::makei(rows, fn(_) { array_size.to_uint64() })
      VectorData::List(child~, offsets~, lengths~)
    }
    _ => {
      // Fallback for types without a dedicated layout (UNION, BIT, TIME_TZ,
      // BIGNUM, ...): per-cell strings.
      let cells : Array[Value] = []
      for row in 0.. VectorChunk? {
  let native_chunk = native_result_fetch_chunk(result)
  if native_is_null_chunk(native_chunk) {
    return None
  }
  let vector_chunk = vector_chunk_from_native_with_names(native_chunk, columns)
  native_chunk_destroy(native_chunk)
  Some(vector_chunk)
}

///|
/// Materialize all columns of a `NativeChunk` into a `VectorChunk`.
/// Column names come from the owning result/stream; pass them explicitly.
fn vector_chunk_from_native_with_names(
  native_chunk : NativeChunk,
  columns : Array[String],
) -> VectorChunk {
  let column_count = native_chunk_column_count(native_chunk)
  let rows = native_chunk_row_count(native_chunk)
  let vectors : Array[Vector] = []
  for col in 0.. Array[String] {
  let column_count = native_result_column_count(result)
  Array::makei(column_count, fn(col) {
    bytes_to_string(native_result_column_name(result, col))
  })
}

///|
/// Column names declared on a result stream.
fn native_stream_column_names(stream : ResultStream) -> Array[String] {
  let column_count = native_stream_column_count(stream)
  Array::makei(column_count, fn(col) {
    bytes_to_string(native_stream_column_name(stream, col))
  })
}

///|
/// Drain a materialized result into `VectorChunk`s (the canonical typed
/// representation), then destroy it.
fn native_result_to_vector_chunks(result : NativeResult) -> Array[VectorChunk] {
  let columns = native_result_column_names(result)
  let chunks : Array[VectorChunk] = []
  for ;; {
    match native_result_next_chunk(result, columns) {
      Some(chunk) => chunks.push(chunk)
      None => break
    }
  } nobreak {
    ()
  }
  native_result_destroy(result)
  chunks
}

// ============================================================================
// Public canonical-chunk API (issue #61)
// ============================================================================

///|
/// Run `sql` and return the result as canonical typed `VectorChunk`s —
/// columnar `Vector`s with owned typed arrays and per-row validity, no
/// per-cell string conversion. Consumed via `Vector::value_at`,
/// `Vector::ints`/`int64s`/`doubles`/..., `VectorChunk::get_value`, or
/// materialized to the compat `QueryResult` via `chunks_to_query_result`.
pub fn Connection::query_chunks(
  self : Connection,
  sql : String,
  on_done~ : (Result[Array[VectorChunk], DuckDBError]) -> Unit,
) -> Unit {
  let result = native_query(self, @encoding/utf8.encode(sql))
  if native_is_null_result(result) {
    on_done(Err(native_typed_error(query_error, "duckdb_query failed")))
  } else {
    on_done(Ok(native_result_to_vector_chunks(result)))
  }
}

///|
/// Execute the prepared statement and return canonical typed `VectorChunk`s.
pub fn PreparedStatement::execute_chunks(
  self : PreparedStatement,
  on_done~ : (Result[Array[VectorChunk], DuckDBError]) -> Unit,
) -> Unit {
  let result = native_execute_prepared(self)
  if native_is_null_result(result) {
    on_done(Err(native_typed_error(query_error, "execute_prepared failed")))
  } else {
    on_done(Ok(native_result_to_vector_chunks(result)))
  }
}

///|
/// Fetch the next chunk of the stream as a canonical typed `VectorChunk`,
/// or `None` at end-of-stream. `ResultStream::next` builds its string
/// `DataChunk` on top of this same path.
pub fn ResultStream::next_chunk(
  self : ResultStream,
  on_done~ : (Result[VectorChunk?, DuckDBError]) -> Unit,
) -> Unit {
  let chunk = native_stream_fetch_chunk(self)
  if native_is_null_chunk(chunk) {
    let msg = bytes_to_string(native_last_error())
    if msg is "" {
      on_done(Ok(None))
    } else {
      on_done(Err(native_typed_error(query_error, "stream fetch failed")))
    }
  } else {
    let vector_chunk = vector_chunk_from_native_with_names(
      chunk,
      native_stream_column_names(self),
    )
    native_chunk_destroy(chunk)
    if vector_chunk.row_count() <= 0 || vector_chunk.column_count() <= 0 {
      on_done(Ok(None))
    } else {
      on_done(Ok(Some(vector_chunk)))
    }
  }
}

// ============================================================================
// Compat row-string materialization on top of chunks (issue #61)
// ============================================================================

///|
/// Materialize one fetched chunk as row-major string cells + null flags via
/// the per-column `duckdb_mb_chunk_col_strings` reader. This is the same
/// canonical chunk the typed path decodes; only the rendering differs.
fn native_chunk_string_rows(
  chunk : NativeChunk,
) -> (Array[Array[String]], Array[Array[Bool]]) {
  let row_count = native_chunk_row_count(chunk)
  let column_count = native_chunk_column_count(chunk)
  let column_cells : Array[FixedArray[Bytes]] = []
  let column_validity : Array[FixedArray[Int]] = []
  for col in 0.. FixedArray[Bool] {
  FixedArray::makei(rows, fn(i) { i < flags.length() && flags[i] == 1 })
}