// Copyright 2026 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

// Decoder interface for token-to-text conversion.
//
// Decoders transform token strings (from vocab lookup) back to text.
// Examples: ByteFallback (byte tokens), Metaspace (▁→space), WordPiece (##).
//
// The decode pipeline is:
//   Token IDs → Vocab Lookup → Token Strings → Decoder → Final Text
//
// Design principles:
// - Pull-based: caller controls flow via output buffer size
// - Batched: processes array of token strings, not one at a time
// - Zero allocation: state lives in caller-provided storage
// - Composable: Sequence decoder chains multiple decoders

#ifndef IREE_TOKENIZER_DECODER_H_
#define IREE_TOKENIZER_DECODER_H_

#include "iree/base/api.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct iree_tokenizer_decoder_t iree_tokenizer_decoder_t;
typedef struct iree_tokenizer_decoder_state_t iree_tokenizer_decoder_state_t;
typedef struct iree_tokenizer_decoder_vtable_t iree_tokenizer_decoder_vtable_t;

//===----------------------------------------------------------------------===//
// Token String List (for batched input)
//===----------------------------------------------------------------------===//

// List of token strings for batched decoder input.
// Each string is a token's text representation from vocab lookup.
typedef struct iree_tokenizer_string_list_t {
  iree_host_size_t count;
  const iree_string_view_t* values;
} iree_tokenizer_string_list_t;

// Creates a string list from an array and count.
static inline iree_tokenizer_string_list_t iree_tokenizer_make_string_list(
    const iree_string_view_t* values, iree_host_size_t count) {
  iree_tokenizer_string_list_t list = {count, values};
  return list;
}

// Returns an empty string list.
static inline iree_tokenizer_string_list_t iree_tokenizer_string_list_empty(
    void) {
  iree_tokenizer_string_list_t list = {0, NULL};
  return list;
}

//===----------------------------------------------------------------------===//
// Decoder Flags
//===----------------------------------------------------------------------===//

// Flags declaring decoder transformation properties.
// Used by Sequence decoder to validate children at construction time.
typedef uint32_t iree_tokenizer_decoder_flags_t;
enum iree_tokenizer_decoder_flags_e {
  IREE_TOKENIZER_DECODER_FLAG_NONE = 0,
  // Output byte count is always <= input byte count.
  // Enables zero-allocation in-place transformation in Sequence decoder.
  IREE_TOKENIZER_DECODER_FLAG_SHRINKING = 1u << 0,
};

//===----------------------------------------------------------------------===//
// Decoder Capabilities (for pre-decode optimization)
//===----------------------------------------------------------------------===//

// Capabilities declaring decoder context-dependency.
// Used by tokenizer to determine if decoded token strings can be pre-computed
// at build time, eliminating runtime decoder work during decode.
typedef uint32_t iree_tokenizer_decoder_capability_t;
enum iree_tokenizer_decoder_capability_e {
  IREE_TOKENIZER_DECODER_CAPABILITY_NONE = 0,
  // Output for each token depends only on that token's text, not on adjacent
  // tokens or stream position. Enables full pre-decode at build time.
  // Examples: Replace, Strip, Passthrough.
  IREE_TOKENIZER_DECODER_CAPABILITY_STATELESS = 1u << 0,
  // Output for the first token in a stream differs from subsequent tokens.
  // Typically the first token omits a leading space that "rest" tokens include.
  // Requires STATELESS to also be set for pre-decode to work.
  // Examples: Metaspace (add_prefix_space), WordPiece.
  IREE_TOKENIZER_DECODER_CAPABILITY_POSITION_SENSITIVE = 1u << 1,
  // Stateless for all tokens EXCEPT byte tokens (<0xHH> pattern).
  // Byte tokens require cross-token UTF-8 accumulation (stateful), but
  // non-byte tokens are pure passthrough. Enables a hybrid pre-decode path:
  // non-byte tokens are pre-decoded at build time, byte tokens are handled
  // with an inline accumulator at decode time.
  // Examples: ByteFallback.
  IREE_TOKENIZER_DECODER_CAPABILITY_STATELESS_EXCEPT_BYTE_TOKENS = 1u << 2,
  // Stateless for most tokens (99.3% of GPT-2 vocab), but some produce
  // partial UTF-8 bytes that must accumulate across token boundaries.
  // Unlike BYTE_TOKENS where byte tokens are identifiable by <0xHH> syntax,
  // partial UTF-8 tokens look like normal tokens (e.g., GPT-2's "Ã" maps to
  // byte 0xC3 which is a UTF-8 lead byte). The build step trial-decodes each
  // token and marks partial ones in a slab-embedded bitmap.
  // Examples: ByteLevel (GPT-2 family).
  IREE_TOKENIZER_DECODER_CAPABILITY_STATELESS_EXCEPT_PARTIAL_UTF8 = 1u << 3,
};

//===----------------------------------------------------------------------===//
// Decoder Base Type
//===----------------------------------------------------------------------===//

// Base decoder structure. All concrete decoder types embed this at
// offset 0. The vtable provides type-specific operations; state_size is
// cached at creation time for efficient state allocation.
struct iree_tokenizer_decoder_t {
  const iree_tokenizer_decoder_vtable_t* vtable;  // Must be at offset 0.
  // Size of state struct, cached at creation.
  iree_host_size_t state_size;
  iree_tokenizer_decoder_capability_t capabilities;  // Pre-decode properties.
};

// Base streaming state structure. All concrete state types embed this at
// offset 0. The decoder back-pointer enables vtable dispatch from state.
struct iree_tokenizer_decoder_state_t {
  const iree_tokenizer_decoder_t* decoder;
};

//===----------------------------------------------------------------------===//
// Decoder VTable
//===----------------------------------------------------------------------===//

// VTable for decoder implementations.
//
// Pull-based processing model:
// - state_process() consumes token strings and writes text output
// - Caller controls batch size via input string count and output buffer
// - Incomplete sequences (e.g., partial byte fallback) buffered in state
// - state_finalize() flushes remaining buffered data
struct iree_tokenizer_decoder_vtable_t {
  void (*destroy)(iree_tokenizer_decoder_t* decoder);
  iree_status_t (*state_initialize)(const iree_tokenizer_decoder_t* decoder,
                                    void* storage,
                                    iree_tokenizer_decoder_state_t** out_state);
  void (*state_deinitialize)(iree_tokenizer_decoder_state_t* state);
  iree_status_t (*state_process)(iree_tokenizer_decoder_state_t* state,
                                 iree_tokenizer_string_list_t token_strings,
                                 iree_mutable_string_view_t output,
                                 iree_host_size_t* out_strings_consumed,
                                 iree_host_size_t* out_bytes_written);
  iree_status_t (*state_finalize)(iree_tokenizer_decoder_state_t* state,
                                  iree_mutable_string_view_t output,
                                  iree_host_size_t* out_written);
  bool (*state_has_pending)(const iree_tokenizer_decoder_state_t* state);
  iree_tokenizer_decoder_flags_t flags;
};

//===----------------------------------------------------------------------===//
// Decoder Public API
//===----------------------------------------------------------------------===//

// Initializes base decoder fields. Called by concrete implementations
// after allocating the decoder struct.
void iree_tokenizer_decoder_initialize(
    iree_tokenizer_decoder_t* decoder,
    const iree_tokenizer_decoder_vtable_t* vtable, iree_host_size_t state_size,
    iree_tokenizer_decoder_capability_t capabilities);

// Frees a decoder. Safe to call with NULL.
void iree_tokenizer_decoder_free(iree_tokenizer_decoder_t* decoder);

// Returns the size of state storage required for this decoder.
static inline iree_host_size_t iree_tokenizer_decoder_state_size(
    const iree_tokenizer_decoder_t* decoder) {
  return decoder->state_size;
}

// Returns the capability flags for this decoder.
static inline iree_tokenizer_decoder_capability_t
iree_tokenizer_decoder_capabilities(const iree_tokenizer_decoder_t* decoder) {
  return decoder->capabilities;
}

// Initializes streaming state in caller-provided storage.
// |storage| must be at least iree_tokenizer_decoder_state_size() bytes.
static inline iree_status_t iree_tokenizer_decoder_state_initialize(
    const iree_tokenizer_decoder_t* decoder, void* storage,
    iree_tokenizer_decoder_state_t** out_state) {
  return decoder->vtable->state_initialize(decoder, storage, out_state);
}

// Deinitializes streaming state (does not free storage).
static inline void iree_tokenizer_decoder_state_deinitialize(
    iree_tokenizer_decoder_state_t* state) {
  if (state && state->decoder) {
    state->decoder->vtable->state_deinitialize(state);
  }
}

// Minimum output buffer size for decoders. Smaller buffers cannot guarantee
// progress on multi-byte sequences (UTF-8 max 4 bytes, replacement char 3).
#define IREE_TOKENIZER_DECODER_MIN_BUFFER_SIZE 4

// Processes a batch of token strings, writing decoded text.
//
// Reads up to |token_strings.count| strings and writes up to |output.size|
// bytes. Returns strings consumed in |out_strings_consumed| and bytes written
// in |out_bytes_written|.
//
// A token string is "consumed" (counted in |out_strings_consumed|) only if it
// has been fully processed or successfully buffered into internal state. If a
// token cannot be accepted (e.g., output buffer full and no internal buffering
// available), it is not counted. The caller should retry with the unconsumed
// tokens after draining output.
//
// May consume fewer strings than count if the output buffer is full or the
// decoder is waiting for more tokens to complete a sequence (e.g., byte
// fallback). May write fewer bytes than output capacity if input is exhausted
// or the decoder needs more tokens to produce output.
static inline iree_status_t iree_tokenizer_decoder_state_process(
    iree_tokenizer_decoder_state_t* state,
    iree_tokenizer_string_list_t token_strings,
    iree_mutable_string_view_t output, iree_host_size_t* out_strings_consumed,
    iree_host_size_t* out_bytes_written) {
  return state->decoder->vtable->state_process(
      state, token_strings, output, out_strings_consumed, out_bytes_written);
}

// Finalizes the stream, flushing all buffered data. Call after all tokens
// have been provided via state_process(). May need multiple calls if the
// output buffer is too small for buffered data.
static inline iree_status_t iree_tokenizer_decoder_state_finalize(
    iree_tokenizer_decoder_state_t* state, iree_mutable_string_view_t output,
    iree_host_size_t* out_written) {
  return state->decoder->vtable->state_finalize(state, output, out_written);
}

// Returns true if state has pending data that would produce output on
// finalize. Useful for checking if more finalize() calls are needed.
static inline bool iree_tokenizer_decoder_state_has_pending(
    const iree_tokenizer_decoder_state_t* state) {
  return state->decoder->vtable->state_has_pending(state);
}

#ifdef __cplusplus
}  // extern "C"
#endif

#endif  // IREE_TOKENIZER_DECODER_H_
