///|
#external
pub type ArrowResult

// ============================================================================
// Arrow FFI Declarations (JavaScript Backend)
// ============================================================================

///|
extern "js" fn js_query_arrow(
  conn : Connection,
  sql : String,
  on_ok : (ArrowResult) -> Unit,
  on_err : (String) -> Unit,
) -> Unit =
  #|(conn, sql, on_ok, on_err) => {
  #|  const run = async () => {
  #|    if (conn && conn.kind === "wasm") {
  #|      try {
  #|        const arrowResult = await conn.conn.query(sql, { returnType: "arrow" });
  #|        on_ok({ kind: "arrow", result: arrowResult });
  #|        return;
  #|      } catch (e) {
  #|        on_err(e.message || String(e));
  #|        return;
  #|      }
  #|    }
  #|    if (conn && conn.kind === "node") {
  #|      // Node API - Arrow support
  #|      try {
  #|        const result = await conn.connection.run(sql);
  #|        if (result && result.arrow) {
  #|          on_ok({ kind: "arrow", result: result.arrow() });
  #|          return;
  #|        }
  #|        // Fallback to regular result
  #|        on_ok({ kind: "arrow", result: null });
  #|        return;
  #|      } catch (e) {
  #|        on_err(e.message || String(e));
  #|        return;
  #|      }
  #|    }
  #|    on_err("unknown connection backend");
  #|  };
  #|  run().catch((err) => on_err(err.message || String(err)));
  #|}

///|
extern "js" fn js_arrow_column_count(result : ArrowResult) -> Int =
  #|(result) => {
  #|  if (result && result.kind === "arrow" && result.result) {
  #|    const arrow = result.result;
  #|    if (arrow.numCols) {
  #|      return arrow.numCols;
  #|    }
  #|    if (arrow.schema && arrow.schema.fields) {
  #|      return arrow.schema.fields.length;
  #|    }
  #|  }
  #|  return 0;
  #|}

///|
extern "js" fn js_arrow_row_count(result : ArrowResult) -> Int =
  #|(result) => {
  #|  if (result && result.kind === "arrow" && result.result) {
  #|    const arrow = result.result;
  #|    if (arrow.numRows !== undefined) {
  #|      return arrow.numRows;
  #|    }
  #|    if (arrow.length !== undefined) {
  #|      return arrow.length;
  #|    }
  #|  }
  #|  return 0;
  #|}

///|
extern "js" fn js_arrow_destroy(result : ArrowResult) -> Unit =
  #|(result) => {
  #|  // Arrow result will be garbage collected
  #|  if (result && result.result && result.result.close) {
  #|    result.result.close();
  #|  }
  #|}

///|
extern "js" fn js_arrow_get_schema(result : ArrowResult) -> String =
  #|(result) => {
  #|  if (!result?.result?.schema?.fields) return "[]";
  #|  const fields = result.result.schema.fields.map(field => {
  #|    let typeId = "string";
  #|    // Arrow type detection
  #|    if (field.type) {
  #|      const t = field.type;
  #|      // Check Arrow type by various properties
  #|      if (t.typeId === 1 || t.bitWidth === 1 || t.constructor?.name === "Bool") typeId = "bool";
  #|      else if (t.typeId === 2 || t.bitWidth === 32 || t.constructor?.name === "Int") typeId = "int32";
  #|      else if (t.typeId === 3 || t.bitWidth === 64 || t.constructor?.name === "Int64" || t.constructor?.name === "BigInt") typeId = "int64";
  #|      else if (t.typeId === 7 || t.typeId === 8 || t.typeId === 9 || t.constructor?.name === "Float" || t.constructor?.name === "Float64") typeId = "double";
  #|      else if (t.typeId === 5 || t.typeId === 6 || t.constructor?.name === "Utf8") typeId = "string";
  #|    }
  #|    return {
  #|      name: field.name || "",
  #|      nullable: field.nullable ?? true,
  #|      type_id: typeId
  #|    };
  #|  });
  #|  return JSON.stringify(fields);
  #|}

///|
extern "js" fn js_arrow_get_column_int32(
  result : ArrowResult,
  col : Int,
) -> String =
  #|(result, col) => {
  #|  if (!result?.result) return "[]";
  #|  const vector = result.result.getChild(col);
  #|  if (!vector) return "[]";
  #|  const arr = vector.toArray();
  #|  return JSON.stringify(arr || []);
  #|}

///|
extern "js" fn js_arrow_get_column_int64(
  result : ArrowResult,
  col : Int,
) -> String =
  #|(result, col) => {
  #|  if (!result?.result) return "[]";
  #|  const vector = result.result.getChild(col);
  #|  if (!vector) return "[]";
  #|  const arr = vector.toArray();
  #|  // Convert BigInt values to strings for JSON serialization
  #|  const converted = arr.map(v => typeof v === "bigint" ? v.toString() : v);
  #|  return JSON.stringify(converted || []);
  #|}

///|
extern "js" fn js_arrow_get_column_double(
  result : ArrowResult,
  col : Int,
) -> String =
  #|(result, col) => {
  #|  if (!result?.result) return "[]";
  #|  const vector = result.result.getChild(col);
  #|  if (!vector) return "[]";
  #|  const arr = vector.toArray();
  #|  return JSON.stringify(arr || []);
  #|}

///|
extern "js" fn js_arrow_get_column_string(
  result : ArrowResult,
  col : Int,
) -> String =
  #|(result, col) => {
  #|  if (!result?.result) return "[]";
  #|  const vector = result.result.getChild(col);
  #|  if (!vector) return "[]";
  #|  const arr = vector.toArray();
  #|  return JSON.stringify(arr || []);
  #|}

///|
extern "js" fn js_arrow_get_column_bool(
  result : ArrowResult,
  col : Int,
) -> String =
  #|(result, col) => {
  #|  if (!result?.result) return "[]";
  #|  const vector = result.result.getChild(col);
  #|  if (!vector) return "[]";
  #|  const arr = vector.toArray();
  #|  return JSON.stringify(arr || []);
  #|}

// ============================================================================
// Public API (JavaScript Backend)
// ============================================================================

///|
pub struct ArrowField {
  name : String
  nullable : Bool
  type_id : String
}

///|
pub struct ArrowSchemaInfo {
  fields : Array[ArrowField]
}

///|
pub fn Connection::query_arrow(
  self : Connection,
  sql : String,
  on_done~ : (Result[ArrowResult, DuckDBError]) -> Unit,
) -> Unit {
  js_query_arrow(self, sql, fn(result) { on_done(Ok(result)) }, fn(err) {
    on_done(Err(DuckDBError::Message(err)))
  })
}

///|
pub fn ArrowResult::column_count(self : ArrowResult) -> Int {
  js_arrow_column_count(self)
}

///|
pub fn ArrowResult::row_count(self : ArrowResult) -> Int {
  js_arrow_row_count(self)
}

///|
pub fn ArrowResult::get_schema(
  self : ArrowResult,
) -> Result[ArrowSchemaInfo, DuckDBError] {
  let json_str = js_arrow_get_schema(self)
  parse_arrow_schema_json(json_str)
}

// Parse Arrow schema JSON into ArrowSchemaInfo

///|
fn parse_arrow_schema_json(
  json : String,
) -> Result[ArrowSchemaInfo, DuckDBError] {
  let parsed = @json.parse(json) catch {
    err =>
      return Err(
        DuckDBError::Message("schema parse failed: " + err.to_string()),
      )
  }
  match parsed {
    Json::Array(items) => {
      let fields : Array[ArrowField] = []
      for item in items {
        match item {
          Json::Object(obj) => {
            let name = match obj["name"] {
              Json::String(n) => n
              _ => ""
            }
            let nullable = match obj["nullable"] {
              Json::True => true
              Json::False => false
              _ => true
            }
            let type_id = match obj["type_id"] {
              Json::String(t) => t
              _ => "string"
            }
            fields.push(ArrowField::{ name, nullable, type_id })
          }
          _ => return Err(DuckDBError::Message("invalid field format"))
        }
      } nobreak {
        ()
      }
      Ok(ArrowSchemaInfo::{ fields, })
    }
    _ => Ok(ArrowSchemaInfo::{ fields: [] })
  }
}

///|
pub fn ArrowResult::get_column_int32(
  self : ArrowResult,
  col : Int,
) -> Array[Int] {
  let json_str = js_arrow_get_column_int32(self, col)
  parse_json_int_array(json_str)
}

///|
pub fn ArrowResult::get_column_int64(
  self : ArrowResult,
  col : Int,
) -> Array[Int] {
  let json_str = js_arrow_get_column_int64(self, col)
  parse_json_int_array(json_str)
}

///|
pub fn ArrowResult::get_column_double(
  self : ArrowResult,
  col : Int,
) -> Array[Double] {
  let json_str = js_arrow_get_column_double(self, col)
  parse_json_double_array(json_str)
}

///|
pub fn ArrowResult::get_column_string(
  self : ArrowResult,
  col : Int,
) -> Array[String] {
  let json_str = js_arrow_get_column_string(self, col)
  parse_json_string_array(json_str)
}

///|
pub fn ArrowResult::get_column_bool(
  self : ArrowResult,
  col : Int,
) -> Array[Bool] {
  let json_str = js_arrow_get_column_bool(self, col)
  parse_json_bool_array(json_str)
}

// ============================================================================
// JSON Parsing Helpers
// ============================================================================

// Simple string to Int parser

///|
fn parse_str_to_int(s : String) -> Int {
  let mut result = 0
  let mut i = 0
  let negative = if s.length() > 0 && s[0] == '-' {
    i = 1
    true
  } else {
    false
  }
  while i < s.length() {
    let c = s[i]
    if c >= '0' && c <= '9' {
      result = result * 10 + (c.to_int() - '0'.to_int())
    }
    i = i + 1
  }
  if negative {
    -result
  } else {
    result
  }
}

///|
fn parse_json_int_array(json : String) -> Array[Int] {
  let parsed = @json.parse(json) catch { _ => return [] }
  match parsed {
    Json::Array(items) => {
      let result : Array[Int] = []
      for item in items {
        match item {
          Json::Number(n, ..) => result.push(parse_str_to_int(n.to_string()))
          Json::String(s) =>
            // Handle BigInt strings
            result.push(parse_str_to_int(s))
          Json::Null => result.push(0)
          _ => result.push(0)
        }
      } nobreak {
        ()
      }
      result
    }
    _ => []
  }
}

///|
fn parse_json_double_array(json : String) -> Array[Double] {
  let parsed = @json.parse(json) catch { _ => return [] }
  match parsed {
    Json::Array(items) => {
      let result : Array[Double] = []
      for item in items {
        match item {
          Json::Number(n, ..) => result.push(n)
          Json::Null => result.push(0.0)
          _ => result.push(0.0)
        }
      } nobreak {
        ()
      }
      result
    }
    _ => []
  }
}

///|
fn parse_json_string_array(json : String) -> Array[String] {
  let parsed = @json.parse(json) catch { _ => return [] }
  match parsed {
    Json::Array(items) => {
      let result : Array[String] = []
      for item in items {
        match item {
          Json::String(s) => result.push(s)
          Json::Null => result.push("")
          _ => result.push("")
        }
      } nobreak {
        ()
      }
      result
    }
    _ => []
  }
}

///|
fn parse_json_bool_array(json : String) -> Array[Bool] {
  let parsed = @json.parse(json) catch { _ => return [] }
  match parsed {
    Json::Array(items) => {
      let result : Array[Bool] = []
      for item in items {
        match item {
          Json::True => result.push(true)
          Json::False => result.push(false)
          Json::Null => result.push(false)
          _ => result.push(false)
        }
      } nobreak {
        ()
      }
      result
    }
    _ => []
  }
}

///|
pub fn ArrowResult::close(
  self : ArrowResult,
  on_done~ : (Result[Unit, DuckDBError]) -> Unit,
) -> Unit {
  js_arrow_destroy(self)
  on_done(Ok(()))
}