// Copyright 2026 Noverberrain.
// SPDX-License-Identifier: Apache-2.0

///|
/// One non-NULL SQL string's exact bytes; no charset conversion or padding.
pub struct DecodedString {
  value : Bytes
  bytes_read : Int32
} derive(Debug, Eq)

///|
pub extend DecodedString with Eq::{equal, not_equal}

///|
pub extend DecodedString with @debug.Debug::{to_repr}

///|
/// Decode a present, non-NULL VARCHAR (15) or genuine STRING (254) value.
/// CHAR/BINARY use STRING; VARCHAR/VARBINARY use VARCHAR. ENUM/SET embedded
/// in STRING metadata are rejected. The result owns only the value's bytes.
/// `file_offset` is this field's first byte; `column_index` is zero-based.
/// `bytes_read` includes the length prefix; trailing fields are not consumed.
/// The caller handles row presence/NULL bitmaps and any charset conversion.
pub fn TableColumn::decode_string(
  self : TableColumn,
  data : BytesView,
  file_offset~ : UInt64,
  column_index~ : Int32,
) -> DecodedString raise DecodeError {
  guard self.wire_type == 15 || self.wire_type == 254 else {
    raise UnsupportedStringColumn(
      file_offset~,
      column_index~,
      wire_type=self.wire_type,
    )
  }
  guard self.metadata.length() == 2 else {
    raise InvalidStringMetadata(
      file_offset~,
      column_index~,
      reason="expected two metadata bytes",
    )
  }
  let first = self.metadata[0].to_int()
  let second = self.metadata[1].to_int()
  let max_length = if self.wire_type == 15 {
    first | (second << 8)
  } else {
    // STRING's first metadata byte mixes the real type with two length bits.
    guard (first | 0x30) == 254 else {
      raise InvalidStringMetadata(
        file_offset~,
        column_index~,
        reason="not a genuine STRING type (ENUM/SET unsupported)",
      )
    }
    (((first & 0x30) ^ 0x30) << 4) | second
  }
  let prefix = if max_length > 255 { 2 } else { 1 }
  guard data.length() >= prefix else {
    raise TruncatedColumnValue(
      file_offset~,
      column_index~,
      expected=prefix,
      actual=data.length(),
    )
  }
  let length = if prefix == 1 {
    data[0].to_int()
  } else {
    data[0].to_int() | (data[1].to_int() << 8)
  }
  guard length <= max_length else {
    raise StringLengthExceeded(
      file_offset~,
      column_index~,
      length~,
      max_length~,
    )
  }
  let bytes_read = prefix + length
  guard data.length() >= bytes_read else {
    raise TruncatedColumnValue(
      file_offset~,
      column_index~,
      expected=bytes_read,
      actual=data.length(),
    )
  }
  { value: data[prefix:bytes_read].to_owned(), bytes_read, }
}