// A QPACK encoder that indexes into the dynamic table (RFC 9204 §2.1, §4.5). It pairs the
// two outputs a real encoder produces: the encoder-stream instructions that populate the
// decoder's dynamic table, and the field section that references those entries. The strategy
// here is the simple, always-index one the RFC permits: a header that matches the static
// table exactly is referenced there directly; otherwise the entry is inserted into the
// dynamic table (with a static name reference when the name is known, else a literal name)
// and the field line references it by relative index. An entry too large for the table falls
// back to a literal field line. Sections use Base = the post-insertion insert count, so every
// dynamic reference is a pre-Base relative index. This closes the loop with the dynamic
// decoder: apply the instructions to a peer table, then decode the section against it.

///|
/// How a single header is rendered in the field section.
priv enum QpackFieldRef {
  RefStatic(Int)
  RefDynamic(Int) // absolute index in the dynamic table
  RefLiteral(Bytes, Bytes)
}

///|
/// Encode `headers` with dynamic indexing over `table` (the encoder's view, mutated by the
/// inserts). Returns the encoder-stream instruction block and the field section. `max_entries`
/// comes from the negotiated maximum table capacity.
pub fn qpack_encode_dynamic(
  headers : Array[(String, String)],
  table : QpackDynamicTable,
  max_entries : Int,
  huffman? : Bool = true,
) -> (Bytes, Bytes) {
  let instr = Buffer()
  let refs : Array[QpackFieldRef] = []
  for header in headers {
    let (name, value) = header
    match qpack_static_find(name, value) {
      Some(index) => refs.push(RefStatic(index))
      None => {
        let name_bytes = @utf8.encode(name)
        let value_bytes = @utf8.encode(value)
        if qpack_entry_size(name_bytes, value_bytes) <= table.capacity {
          let inst = match qpack_static_find_name(name) {
            Some(name_index) =>
              InsertNameRef(is_static=true, index=name_index, value=value_bytes)
            None => InsertLiteralName(name=name_bytes, value=value_bytes)
          }
          instr.write_bytes(qpack_encode_encoder_inst(inst, huffman~))
          // apply cannot raise here: the name index (if any) is a valid static entry and
          // the value fits the capacity.
          let _ = table.apply(inst) catch { _ => false }
          refs.push(RefDynamic(table.insert_count - 1))
        } else {
          refs.push(RefLiteral(name_bytes, value_bytes))
        }
      }
    }
  }
  let base = table.insert_count
  let section = Buffer()
  section.write_bytes(qpack_encode_section_prefix(base, base, max_entries))
  for r in refs {
    match r {
      RefStatic(index) => section.write_bytes(qpack_int_encode(index, 6, 0xc0))
      RefDynamic(abs) =>
        section.write_bytes(qpack_int_encode(base - 1 - abs, 6, 0x80))
      RefLiteral(name, value) => {
        section.write_bytes(qpack_encode_string(name, 3, 0x20, huffman))
        section.write_bytes(qpack_encode_string(value, 7, 0, huffman))
      }
    }
  }
  (instr.to_bytes(), section.to_bytes())
}