#' @importFrom grumpy convert_bytes_to_array
codec_bytes_decode <- function(
  input,
  chunk_dim,
  datatype,
  endian,
  ...
) {
  convert_bytes_to_array(
    input,
    datatype$base_type,
    chunk_dim,
    datatype$nbytes,
    endian
  )
}

codec_bytes_encode <- function(input, datatype, endian) {
  if (is.character(input)) {
    ## we need to create fixed length strings either via padding or trimming
    if (datatype$base_type == "unicode") {
      to <- if (endian == "little") "UCS-4LE" else "UCS-4BE"
      raw_list <- iconv(input, to = to, toRaw = TRUE)
    } else {
      raw_list <- iconv(input, toRaw = TRUE)
    }
    unlist(
      lapply(
        raw_list,
        FUN = function(x, nbytes) {
          # NULL generated by iconv() for NA_character_
          if (is.null(x)) {
            x <- writeBin(NA_integer_, raw())
          }
          length(x) <- nbytes
          return(x)
        },
        nbytes = datatype$nbytes
      )
    )
  } else {
    if (is.na(endian)) {
      endian <- "little"
    }
    writeBin(input, raw(), size = datatype$nbytes, endian = endian)
  }
}


# -- Variable-length UTF-8 ------------------------
`codec_vlen-utf8_encode` <- function(input, ...) {
  raw_nvalues <- writeBin(length(input), raw(), size = 4L, endian = "little")
  # charToRaw() converts NA_character_ to "NA"
  raw_strings <- lapply(input, function(x) charToRaw(enc2utf8(x)))
  raw_string_lens <- .Call(
    "chop_vec",
    writeBin(lengths(raw_strings), raw(), size = 4L),
    4L,
    PACKAGE = "Rarr"
  )

  raw_vlen_utf8 <- c(
    raw_nvalues,
    unlist(mapply(
      function(len, str) c(len, str),
      raw_string_lens,
      raw_strings
    ))
  )
  return(raw_vlen_utf8)
}

`codec_vlen-utf8_decode` <- function(input, chunk_dim, ...) {
  .Call("codec_vlen_utf8_decode_c", input, chunk_dim, PACKAGE = "Rarr")
}

codec_sharding_indexed_decode <- function(
  input,
  shard_dim,
  datatype,
  fill_value,
  ...
) {
  config <- list(...)
  chunk_dim <- unlist(config$chunk_shape)
  nb_dims <- length(shard_dim)
  index_shape <- c(2L, shard_dim / chunk_dim)
  index_length <- prod(index_shape)
  index_nbytes <- index_length * 8L
  names(config$index_codecs) <- vapply(
    config$index_codecs,
    function(x) x$name,
    character(1L)
  )
  if ("crc32c" %in% names(config$index_codecs)) {
    index_nbytes <- index_nbytes + 4L
  }

  index_location <- config$index_location %||% "end"
  if (index_location == "end") {
    index_raw <- input[seq.int(to = length(input), length.out = index_nbytes)]
  } else {
    index_raw <- input[seq_len(index_nbytes)]
  }

  index <- readBin(
    index_raw,
    what = "integer",
    n = index_length,
    size = 8L,
    endian = config$index_codecs$bytes$configuration[["endian"]] %||%
      .Platform$endian
  )
  dim(index) <- index_shape
  index <- codec_transpose_decode(index, c(1L, rev(seq_len(nb_dims) + 1L)))

  configured_decoders <- config$codecs |>
    setNames(vapply(
      config$codecs,
      function(x) x$name,
      FUN.VALUE = character(1L)
    )) |>
    .configure_codecs(operation = "decode")

  chunks <- apply(
    index,
    seq_len(nb_dims) + 1L,
    function(x) {
      chunk_offset <- x[[1L]]
      chunk_nbytes <- x[[2L]]
      if (chunk_nbytes <= 0L) {
        return()
      }
      # Offset caused by index being at the start is already contained in the
      # offset returned by index.
      chunk_raw <- input[seq.int(
        chunk_offset + 1L,
        length.out = chunk_nbytes
      )]
      # FIXME: Once stores are implemented, treat this as a memory store and use `read_data()` directly
      read_chunk(
        chunk_raw,
        chunk_dim = chunk_dim,
        decoders = configured_decoders,
        datatype = datatype,
        fill_value = fill_value
      )
    }
  )
  shard <- array(fill_value, dim = shard_dim)
  non_empty_coords <- which(
    asplit(index, 1L)[[2L]] > 0L,
    arr.ind = TRUE
  )
  # Merge chunks into shard
  for (i in seq_len(nrow(non_empty_coords))) {
    coords_in_shard <- non_empty_coords[i, ]
    index_in_result <- mapply(
      FUN = function(coord, chunk_dim) {
        seq.int((coord - 1L) * chunk_dim + 1L, length.out = chunk_dim)
      },
      coords_in_shard,
      chunk_dim = chunk_dim,
      SIMPLIFY = FALSE
    )
    chunk_to_insert <- drop(.extract_chunk(chunks, coords_in_shard))[[1L]]
    rlang::inject(shard[!!!index_in_result] <- chunk_to_insert) # nolint implicit_assignment_linter.
  }

  return(shard)
}
