// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// A statement's answer, read as a token stream: metadata opens a set, `ROW` fills it,
// `DONE` closes it, and an `ERROR` is a fact about the call rather than the end of the
// message.

///|
/// What a statement returned: one array of rows per result set, how many rows it changed,
/// and what the server said about itself.
priv struct Response {
  sets : Array[Array[@moondb.Row]]
  rows_affected : Int64
  failure : ServerMessage?
  /// A new transaction descriptor, when the server changed one: a `BEGIN` hands out the
  /// descriptor every later request inside the transaction has to carry.
  txn : Bytes?
  /// The integer a procedure returned through an `OUTPUT` parameter, when it returned one.
  /// `sp_prepare` hands back its statement handle this way.
  return_value : Int32?
}

///|
/// Parse a statement's whole answer.
fn parse_response(payload : Bytes) -> Response raise @moondb.DbError {
  let c = Cursor::new(payload)
  let sets : Array[Array[@moondb.Row]] = []
  let mut failure : ServerMessage? = None
  let mut txn : Bytes? = None
  let mut rows_affected = 0L
  let mut return_value : Int32? = None
  let mut current : Array[Column] = []
  let mut rows : Array[@moondb.Row] = []
  let mut open = false
  while !c.done() {
    let token = c.u8()
    match token {
      0x81 => {
        if open {
          sets.push(rows)
        }
        let count = c.u16le()
        let here : Array[Column] = []
        if count != 0xFFFF {
          for i = 0; i < count; i = i + 1 {
            here.push(read_column(c))
          }
        }
        current = here
        rows = []
        open = true
      }
      0xD1 => {
        let names : Array[String] = []
        let values : Array[@moondb.Value] = []
        for col in current {
          names.push(col.name)
          values.push(read_value(c, col))
        }
        let row : @moondb.Row = { columns: names, values, }
        rows.push(row)
      }
      0xD2 => {
        let count = current.length()
        let bitmap = c.take((count + 7) / 8)
        let names : Array[String] = []
        let values : Array[@moondb.Value] = []
        for i = 0; i < count; i = i + 1 {
          let col = current[i]
          names.push(col.name)
          let is_null = ((bitmap[i / 8].to_int() >> (i % 8)) & 1) == 1
          if is_null {
            values.push(@moondb.Null)
          } else {
            values.push(read_value(c, col))
          }
        }
        let row : @moondb.Row = { columns: names, values, }
        rows.push(row)
      }
      0xAA => {
        let length = c.u16le()
        let body = Cursor::new(c.take(length))
        let detail = read_server_message(body)
        // A failed batch reports every error it hit and then a summary — "Statement(s)
        // could not be prepared" — which says nothing about what was wrong. The first one
        // is the specific one, so it is the one kept.
        if failure is None {
          failure = Some(detail)
        }
      }
      0xAB => {
        let length = c.u16le()
        let body = Cursor::new(c.take(length))
        read_server_message(body) |> ignore
      }
      0xAD | 0xA9 | 0xA4 | 0xA5 | 0xED => {
        let length = c.u16le()
        c.skip(length)
      }
      // Environment changes carry the transaction descriptor: type 8 begins one, 9 and 10
      // end it, and either way the next request has to know.
      0xE3 => {
        let length = c.u16le()
        match read_envchange(c, length) {
          Some(descriptor) => txn = Some(descriptor)
          None => ()
        }
      }
      // Session state carries a four-byte length, not the two every token beside it uses.
      0xE4 => {
        let length = c.u32le()
        c.skip(length)
      }
      // Feature-extension acknowledgement is a list of feature records with no length of
      // its own: an id, a four-byte size, the body, until a 0xFF terminator.
      0xAE =>
        for ;; {
          let feature = c.u8()
          if feature == 0xFF {
            break
          }
          let length = c.u32le()
          c.skip(length)
        }
      0x79 => c.u32le() |> ignore
      // RETURNVALUE: what an OUTPUT parameter came back holding. `sp_prepare` returns its
      // statement handle this way, so the value is read out rather than skipped.
      0xAC =>
        match read_return_value(c) {
          Some(value) => return_value = Some(value)
          None => ()
        }
      0xFD | 0xFE | 0xFF => {
        let status = c.u16le()
        c.u16le() |> ignore
        let count = c.u64le()
        if count > 0L || (status & 0x10) != 0 {
          rows_affected = count
        }
      }
      _ =>
        raise @moondb.QueryError(
          "tds: unknown token 0x" + token.to_string() + " in the response",
        )
    }
  }
  if open {
    sets.push(rows)
  }
  { sets, rows_affected, failure, txn, return_value, }
}

///|
/// One `RETURNVALUE`: the ordinal and name of the parameter it belongs to, then the value in
/// the same type-tagged form an RPC parameter uses. Only an integer is of any use here — the
/// handle `sp_prepare` hands out — so everything else is read past and reported as `None`.
fn read_return_value(c : Cursor) -> Int32? raise @moondb.DbError {
  c.u16le() |> ignore // ordinal
  let chars = c.u8()
  c.skip(chars * 2) // the parameter's name
  c.u8() |> ignore // status: the OUTPUT flag
  c.u32le() |> ignore // user type
  c.u16le() |> ignore // flags
  let ty = c.u8()
  // INTN carries its own length, so a four- or eight-byte integer reads the same way.
  if ty == 0x26 {
    c.u8() |> ignore // the maximum length the parameter was declared with
    let length = c.u8()
    if length == 0 {
      return None
    }
    let low = c.u32le()
    if length == 8 {
      c.u32le() |> ignore
    }
    return Some(low)
  }
  raise @moondb.Server({
    kind: @moondb.ErrorKind::NotSupported,
    sqlstate: None,
    code: None,
    message: "moonsqls cannot read a " + type_name(ty) + " return value",
    detail: None,
    hint: None,
    severity: None,
  })
}

///|
/// `ERROR`/`INFO` fields, read from the token's own body so the names and line number that
/// trail them cannot make the read run into the next token.
fn read_server_message(c : Cursor) -> ServerMessage raise @moondb.DbError {
  let number = c.u32le()
  c.u8() |> ignore // state
  let severity = c.u8()
  let chars = c.u16le()
  let text = c.ucs2(chars)
  { number, severity, text, }
}

///|
/// The entries of an `ENVCHANGE`: a kind, a new value and an old one, each with its own
/// length. Only the transaction kinds matter here.
fn read_envchange(c : Cursor, length : Int32) -> Bytes? raise @moondb.DbError {
  let body = Cursor::new(c.take(length))
  let mut txn : Bytes? = None
  while !body.done() {
    let kind = body.u8()
    // A database, language, charset or packet-size value is a character string: its
    // length counts characters and its bytes are twice that. Everything else — the
    // transaction descriptor included — is a byte string.
    let new_len = body.u8()
    let new_value = if new_len == 0 {
      b""
    } else if kind == 1 || kind == 2 || kind == 3 || kind == 4 {
      body.take(new_len * 2)
    } else {
      body.take(new_len)
    }
    let old_len = body.u8()
    if old_len > 0 {
      body.skip(
        if kind == 1 || kind == 2 || kind == 3 || kind == 4 {
          old_len * 2
        } else {
          old_len
        },
      )
    }
    if kind == 8 && new_len == 8 {
      txn = Some(new_value)
    }
    if kind == 9 || kind == 10 {
      txn = Some(b"\x00\x00\x00\x00\x00\x00\x00\x00")
    }
  }
  txn
}

///|
/// SQL Server's error numbers, classified the way the rest of this repository classifies a
/// failure: a violated constraint is `Integrity`, a statement the parser rejected is
/// `Programming`, a login or permission failure is `Operational`.
fn error_of(message : ServerMessage) -> @moondb.DbError {
  let kind = match message.number {
    2627 | 2601 | 547 => @moondb.ErrorKind::Integrity
    102 | 105 | 156 | 207 | 208 | 2812 | 1038 => @moondb.ErrorKind::Programming
    4060 | 18456 | 229 => @moondb.ErrorKind::Operational
    _ => @moondb.ErrorKind::Database
  }
  @moondb.Server({
    kind,
    sqlstate: None,
    code: Some(message.number),
    message: message.text,
    detail: Some("SQL Server error " + message.number.to_string()),
    hint: None,
    severity: Some(message.severity.to_string()),
  })
}