package qrcode

import "core:slice"

VERSION_MIN :: 1
VERSION_MAX :: 40

// The worst-case number of bytes needed to store one QR Code, up to and including version 40.
BUFFER_LEN_MAX :: 3918 // buffer_len_for_version(VERSION_MAX)

// Returns the number of bytes needed to store any QR Code up to and including the given version.
buffer_len_for_version :: #force_inline proc(n: int) -> int {
	size := n * 4 + 17
	return (size * size + 7) / 8 + 1
}

@(private)
LENGTH_OVERFLOW :: -1
@(private)
REED_SOLOMON_DEGREE_MAX :: 30
@(private)
PENALTY_N1 :: 3
@(private)
PENALTY_N2 :: 3
@(private)
PENALTY_N3 :: 40
@(private)
PENALTY_N4 :: 10

// The error correction level in a QR Code symbol.
Ecc :: enum {
	Low, // The QR Code can tolerate about  7% erroneous codewords
	Medium, // The QR Code can tolerate about 15% erroneous codewords
	Quartile, // The QR Code can tolerate about 25% erroneous codewords
	High, // The QR Code can tolerate about 30% erroneous codewords
}

// The mask pattern used in a QR Code symbol.
Mask :: enum u8 {
	M0,
	M1,
	M2,
	M3,
	M4,
	M5,
	M6,
	M7,
}

// Describes how a segment's data bits are interpreted.
Mode :: enum {
	Numeric      = 0x1,
	Alphanumeric = 0x2,
	Byte         = 0x4,
	Kanji        = 0x8,
	Eci          = 0x7,
}

// A segment of character/binary/control data in a QR Code symbol.
Segment :: struct {
	mode:       Mode,
	num_chars:  int,
	data:       []u8, // Slice into caller-provided buffer. Not owned.
	bit_length: int,
}

//odinfmt: disable
// For generating error correction codes. Index 0 is padding (set to illegal value).
@(private)
ECC_CODEWORDS_PER_BLOCK := [4][41]i8{
	{-1,  7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low
	{-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium
	{-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile
	{-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High
}

@(private)
NUM_ERROR_CORRECTION_BLOCKS := [4][41]i8{
	{-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4,  4,  4,  4,  4,  6,  6,  6,  6,  7,  8,  8,  9,  9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
	{-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5,  5,  8,  9,  9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
	{-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8,  8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
	{-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
}
//odinfmt: enable

// ---------------------------------------------------------------------------------------------------------------------
// ----- Encode Procedures ------------------------
// ---------------------------------------------------------------------------------------------------------------------

// Encodes the given text string to a QR Code, automatically selecting
// numeric, alphanumeric, or byte mode based on content.
//
// Parameters:
//   text         - [in]      UTF-8 encoded input string.
//   temp_buffer  - [scratch] Temporary workspace, clobbered during encoding. Contents are
//                            undefined after return. Must not alias qrcode.
//   qrcode       - [out]     On success, contains the encoded QR Code (byte 0 = side length,
//                            remaining bytes = module bits). On failure, qrcode[0] is set to 0.
//
// Both temp_buffer and qrcode must have length >= buffer_len_for_version(max_version).
//
// Returns ok=false when:
//   - The text cannot fit in any version within [min_version, max_version] at the given ECL.
//   - The encoded segment data exceeds the buffer capacity.
@(require_results)
encode_text_manual :: proc(
	text: string,
	temp_buffer, qrcode: []u8,
	ecl: Ecc,
	min_version: int = VERSION_MIN,
	max_version: int = VERSION_MAX,
	mask: Maybe(Mask) = nil,
	boost_ecl: bool = true,
) -> (
	ok: bool,
) {
	text_len := len(text)
	if text_len == 0 {
		return encode_segments_advanced_manual(
			nil,
			ecl,
			min_version,
			max_version,
			mask,
			boost_ecl,
			temp_buffer,
			qrcode,
		)
	}
	buf_len := buffer_len_for_version(max_version)

	seg: Segment
	encode: {
		if is_numeric(text) {
			if calc_segment_buffer_size(.Numeric, text_len) > buf_len do break encode
			seg = make_numeric(text, temp_buffer)
		} else if is_alphanumeric(text) {
			if calc_segment_buffer_size(.Alphanumeric, text_len) > buf_len do break encode
			seg = make_alphanumeric(text, temp_buffer)
		} else {
			seg.bit_length = calc_segment_bit_length(.Byte, text_len)
			if seg.bit_length == LENGTH_OVERFLOW || text_len > buf_len do break encode
			for i in 0 ..< text_len {
				temp_buffer[i] = text[i]
			}
			seg.mode = .Byte
			seg.num_chars = text_len
			seg.data = temp_buffer[:text_len]
		}
		segs := [1]Segment{seg}
		return encode_segments_advanced_manual(
			segs[:],
			ecl,
			min_version,
			max_version,
			mask,
			boost_ecl,
			temp_buffer,
			qrcode,
		)
	}
	qrcode[0] = 0
	return false
}

// Encodes text to a QR Code, automatically allocating and freeing the temp buffer.
//
// Parameters:
//   qrcode         - [out] On success, contains the encoded QR Code. On failure, qrcode[0] is
//                          set to 0.
//   temp_allocator - Allocator used for the internal scratch buffer. Freed before return.
//
// Returns ok=false when:
//   - The text cannot fit in any version within [min_version, max_version] at the given ECL.
//   - The temp_allocator fails to allocate.
@(require_results)
encode_text_auto :: proc(
	text: string,
	qrcode: []u8,
	ecl: Ecc,
	min_version: int = VERSION_MIN,
	max_version: int = VERSION_MAX,
	mask: Maybe(Mask) = nil,
	boost_ecl: bool = true,
	temp_allocator := context.temp_allocator,
) -> (
	ok: bool,
) {
	size, size_ok := min_buffer_size(text, ecl, min_version, max_version)
	if !size_ok {
		qrcode[0] = 0
		return false
	}
	temp_buffer, alloc_err := make([]u8, size, temp_allocator)
	if alloc_err != nil {
		qrcode[0] = 0
		return false
	}
	defer delete(temp_buffer, temp_allocator)
	return encode_text_manual(text, temp_buffer, qrcode, ecl, min_version, max_version, mask, boost_ecl)
}


// Encodes arbitrary binary data to a QR Code using byte mode.
//
// Parameters:
//   data_and_temp - [in,scratch] The first data_len bytes are the input payload. The entire
//                                slice is used as temporary workspace during encoding and its
//                                contents are undefined after return. Must not alias qrcode.
//   data_len      - Number of input bytes in data_and_temp.
//   qrcode        - [out]        On success, contains the encoded QR Code. On failure,
//                                qrcode[0] is set to 0.
//
// Both data_and_temp and qrcode must have length >= buffer_len_for_version(max_version).
//
// Returns ok=false when:
//   - The payload cannot fit in any version within [min_version, max_version] at the given ECL.
@(require_results)
encode_binary_manual :: proc(
	data_and_temp: []u8,
	data_len: int,
	qrcode: []u8,
	ecl: Ecc,
	min_version: int = VERSION_MIN,
	max_version: int = VERSION_MAX,
	mask: Maybe(Mask) = nil,
	boost_ecl: bool = true,
) -> (
	ok: bool,
) {
	seg: Segment
	seg.mode = .Byte
	seg.bit_length = calc_segment_bit_length(.Byte, data_len)
	if seg.bit_length == LENGTH_OVERFLOW {
		qrcode[0] = 0
		return false
	}
	seg.num_chars = data_len
	seg.data = data_and_temp[:data_len]
	segs := [1]Segment{seg}
	return encode_segments_advanced_manual(
		segs[:],
		ecl,
		min_version,
		max_version,
		mask,
		boost_ecl,
		data_and_temp,
		qrcode,
	)
}

// Encodes arbitrary binary data to a QR Code using byte mode,
// automatically allocating and freeing the temp buffer.
//
// Parameters:
//   bin_data       - [in]  Payload bytes (aliased by the internal segment; not modified).
//   qrcode         - [out] On success, contains the encoded QR Code. On failure, qrcode[0] is
//                          set to 0.
//   temp_allocator - Allocator used for the internal scratch buffer. Freed before return.
//
// qrcode must have length >= buffer_len_for_version(max_version).
//
// Returns ok=false when:
//   - The payload cannot fit in any version within [min_version, max_version] at the given ECL.
//   - The temp_allocator fails to allocate.
@(require_results)
encode_binary_auto :: proc(
	bin_data: []u8,
	qrcode: []u8,
	ecl: Ecc,
	min_version: int = VERSION_MIN,
	max_version: int = VERSION_MAX,
	mask: Maybe(Mask) = nil,
	boost_ecl: bool = true,
	temp_allocator := context.temp_allocator,
) -> (
	ok: bool,
) {
	seg: Segment
	seg.mode = .Byte
	seg.bit_length = calc_segment_bit_length(.Byte, len(bin_data))
	if seg.bit_length == LENGTH_OVERFLOW {
		qrcode[0] = 0
		return false
	}
	seg.num_chars = len(bin_data)
	seg.data = bin_data
	segs := [1]Segment{seg}
	return encode_segments_advanced_auto(
		segs[:],
		ecl,
		min_version,
		max_version,
		mask,
		boost_ecl,
		qrcode,
		temp_allocator,
	)
}

// Encodes the given segments to a QR Code using default parameters
// (VERSION_MIN..VERSION_MAX, auto mask, boost ECL).
//
// Parameters:
//   segs        - [in]      Slice of segments to encode. Segment data buffers may alias temp_buffer.
//   temp_buffer - [scratch] Temporary workspace, clobbered during encoding. Must not alias qrcode.
//   qrcode      - [out]     On success, contains the encoded QR Code. On failure, qrcode[0] is
//                           set to 0.
//
// Both temp_buffer and qrcode must have length >= BUFFER_LEN_MAX.
//
// Returns ok=false when:
//   - The total segment data exceeds the capacity of version 40 at the given ECL.
@(require_results)
encode_segments_manual :: proc(segs: []Segment, ecl: Ecc, temp_buffer, qrcode: []u8) -> (ok: bool) {
	return encode_segments_advanced_manual(segs, ecl, VERSION_MIN, VERSION_MAX, nil, true, temp_buffer, qrcode)
}

// Encodes segments to a QR Code using default parameters, automatically allocating the temp buffer.
//
// Parameters:
//   segs           - [in]  Slice of segments to encode.
//   qrcode         - [out] On success, contains the encoded QR Code. On failure, qrcode[0] is
//                          set to 0.
//   temp_allocator - Allocator used for the internal scratch buffer. Freed before return.
//
// qrcode must have length >= BUFFER_LEN_MAX.
//
// Returns ok=false when:
//   - The total segment data exceeds the capacity of version 40 at the given ECL.
//   - The temp_allocator fails to allocate.
@(require_results)
encode_segments_auto :: proc(
	segs: []Segment,
	ecl: Ecc,
	qrcode: []u8,
	temp_allocator := context.temp_allocator,
) -> (
	ok: bool,
) {
	size, size_ok := min_buffer_size(segs, ecl)
	if !size_ok {
		qrcode[0] = 0
		return false
	}
	temp_buffer, alloc_err := make([]u8, size, temp_allocator)
	if alloc_err != nil {
		qrcode[0] = 0
		return false
	}
	defer delete(temp_buffer, temp_allocator)
	return encode_segments_manual(segs, ecl, temp_buffer, qrcode)
}


// Encodes the given segments to a QR Code with full control over version range, mask, and ECL boosting.
//
// Parameters:
//   segs        - [in]      Slice of segments to encode. Segment data buffers may alias temp_buffer.
//   temp_buffer - [scratch] Temporary workspace, clobbered during encoding. Must not alias qrcode.
//   qrcode      - [out]     On success, contains the encoded QR Code. On failure, qrcode[0] is
//                           set to 0.
//   mask        - nil for automatic mask selection, or a specific Mask value.
//   boost_ecl   - If true, the ECL may be raised above ecl when the data fits without
//                 increasing the version.
//
// Both temp_buffer and qrcode must have length >= buffer_len_for_version(max_version).
//
// Returns ok=false when:
//   - The total segment data exceeds the capacity of every version in [min_version, max_version]
//     at the given ECL.
@(require_results)
encode_segments_advanced_manual :: proc(
	segs: []Segment,
	ecl: Ecc,
	min_version, max_version: int,
	mask: Maybe(Mask),
	boost_ecl: bool,
	temp_buffer, qrcode: []u8,
) -> (
	ok: bool,
) {
	assert(
		VERSION_MIN <= min_version && min_version <= max_version && max_version <= VERSION_MAX,
		"version range out of bounds",
	)

	ecl := ecl

	// Find the minimal version number to use
	version, data_used_bits: int
	for version = min_version;; version += 1 {
		data_capacity_bits := get_num_data_codewords(version, ecl) * 8
		data_used_bits = get_total_bits(segs, version)
		if data_used_bits != LENGTH_OVERFLOW && data_used_bits <= data_capacity_bits {
			break
		}
		if version >= max_version {
			qrcode[0] = 0
			return false
		}
	}
	assert(data_used_bits != LENGTH_OVERFLOW, "data bits overflowed")

	// Increase the error correction level while the data still fits
	if boost_ecl {
		for e in Ecc.Medium ..= Ecc.High {
			if data_used_bits <= get_num_data_codewords(version, e) * 8 {
				ecl = e
			}
		}
	}

	// Concatenate all segments to create the data bit string
	buf_len := buffer_len_for_version(version)
	slice.zero(qrcode[:buf_len])
	bit_len := 0
	for &seg in segs {
		append_bits_to_buffer(uint(seg.mode), 4, qrcode, &bit_len)
		append_bits_to_buffer(uint(seg.num_chars), num_char_count_bits(seg.mode, version), qrcode, &bit_len)
		for j in 0 ..< seg.bit_length {
			bit := uint((seg.data[j >> 3] >> uint(7 - (j & 7))) & 1)
			append_bits_to_buffer(bit, 1, qrcode, &bit_len)
		}
	}
	assert(bit_len == data_used_bits, "bit length mismatch after segment concatenation")

	// Add terminator and pad up to a byte if applicable
	data_capacity_bits := get_num_data_codewords(version, ecl) * 8
	assert(bit_len <= data_capacity_bits, "data exceeds capacity")
	terminator_bits := min(data_capacity_bits - bit_len, 4)
	append_bits_to_buffer(0, terminator_bits, qrcode, &bit_len)
	append_bits_to_buffer(0, (8 - bit_len % 8) % 8, qrcode, &bit_len)
	assert(bit_len % 8 == 0, "bit length not byte-aligned after padding")

	// Pad with alternating bytes until data capacity is reached
	pad_byte: u8 = 0xEC
	for bit_len < data_capacity_bits {
		append_bits_to_buffer(uint(pad_byte), 8, qrcode, &bit_len)
		pad_byte ~= 0xEC ~ 0x11
	}

	// Compute ECC, draw modules
	add_ecc_and_interleave(qrcode, version, ecl, temp_buffer)
	initialize_function_modules(version, qrcode)
	draw_codewords(temp_buffer, get_num_raw_data_modules(version) / 8, qrcode)
	draw_light_function_modules(qrcode, version)
	initialize_function_modules(version, temp_buffer)

	// Do masking
	chosen_mask: Mask
	if m, m_ok := mask.?; m_ok {
		chosen_mask = m
	} else {
		// Automatically choose best mask
		min_penalty := max(int)
		for msk in Mask {
			apply_mask(temp_buffer, qrcode, msk)
			draw_format_bits(ecl, msk, qrcode)
			penalty := get_penalty_score(qrcode)
			if penalty < min_penalty {
				chosen_mask = msk
				min_penalty = penalty
			}
			apply_mask(temp_buffer, qrcode, msk) // Undo mask via XOR
		}
	}
	apply_mask(temp_buffer, qrcode, chosen_mask)
	draw_format_bits(ecl, chosen_mask, qrcode)
	return true
}

// Encodes segments with full control over parameters, automatically allocating the temp buffer.
//
// Parameters:
//   segs           - [in]  Slice of segments to encode.
//   qrcode         - [out] On success, contains the encoded QR Code. On failure, qrcode[0] is
//                          set to 0.
//   temp_allocator - Allocator used for the internal scratch buffer. Freed before return.
//
// qrcode must have length >= buffer_len_for_version(max_version).
//
// Returns ok=false when:
//   - The total segment data exceeds the capacity of every version in [min_version, max_version]
//     at the given ECL.
//   - The temp_allocator fails to allocate.
@(require_results)
encode_segments_advanced_auto :: proc(
	segs: []Segment,
	ecl: Ecc,
	min_version, max_version: int,
	mask: Maybe(Mask),
	boost_ecl: bool,
	qrcode: []u8,
	temp_allocator := context.temp_allocator,
) -> (
	ok: bool,
) {
	size, size_ok := min_buffer_size(segs, ecl, min_version, max_version)
	if !size_ok {
		qrcode[0] = 0
		return false
	}
	temp_buffer, alloc_err := make([]u8, size, temp_allocator)
	if alloc_err != nil {
		qrcode[0] = 0
		return false
	}
	defer delete(temp_buffer, temp_allocator)
	return encode_segments_advanced_manual(
		segs,
		ecl,
		min_version,
		max_version,
		mask,
		boost_ecl,
		temp_buffer,
		qrcode,
	)
}

encode_manual :: proc {
	encode_text_manual,
	encode_binary_manual,
	encode_segments_manual,
	encode_segments_advanced_manual,
}

encode_auto :: proc {
	encode_text_auto,
	encode_binary_auto,
	encode_segments_auto,
	encode_segments_advanced_auto,
}


// ---------------------------------------------------------------------------------------------------------------------
// ----- Error Correction Code Generation ------------------------
// ---------------------------------------------------------------------------------------------------------------------

// Appends error correction bytes to each block of data, then interleaves bytes from all blocks.
@(private)
add_ecc_and_interleave :: proc(data: []u8, version: int, ecl: Ecc, result: []u8) {
	assert(VERSION_MIN <= version && version <= VERSION_MAX, "version out of bounds")
	num_blocks := int(NUM_ERROR_CORRECTION_BLOCKS[int(ecl)][version])
	block_ecc_len := int(ECC_CODEWORDS_PER_BLOCK[int(ecl)][version])
	raw_codewords := get_num_raw_data_modules(version) / 8
	data_len := get_num_data_codewords(version, ecl)
	num_short_blocks := num_blocks - raw_codewords % num_blocks
	short_block_data_len := raw_codewords / num_blocks - block_ecc_len

	rsdiv: [REED_SOLOMON_DEGREE_MAX]u8
	reed_solomon_compute_divisor(block_ecc_len, rsdiv[:block_ecc_len])

	dat_offset := 0
	for i in 0 ..< num_blocks {
		dat_len := short_block_data_len + (0 if i < num_short_blocks else 1)
		ecc := data[data_len:] // Temporary storage
		reed_solomon_compute_remainder(
			data[dat_offset:dat_offset + dat_len],
			rsdiv[:block_ecc_len],
			ecc[:block_ecc_len],
		)
		k := i
		for j in 0 ..< dat_len {
			if j == short_block_data_len {
				k -= num_short_blocks
			}
			result[k] = data[dat_offset + j]
			k += num_blocks
		}
		k = data_len + i
		for j in 0 ..< block_ecc_len {
			result[k] = ecc[j]
			k += num_blocks
		}
		dat_offset += dat_len
	}
}

// Returns the number of 8-bit data codewords (not ECC) for the given version and ECC level.
@(private)
get_num_data_codewords :: proc(version: int, ecl: Ecc) -> int {
	e := int(ecl)
	return(
		get_num_raw_data_modules(version) / 8 -
		int(ECC_CODEWORDS_PER_BLOCK[e][version]) * int(NUM_ERROR_CORRECTION_BLOCKS[e][version]) \
	)
}

// Returns the number of data bits that can be stored in a QR Code of the given version number.
@(private)
get_num_raw_data_modules :: proc(ver: int) -> int {
	assert(VERSION_MIN <= ver && ver <= VERSION_MAX, "version out of bounds")
	result := (16 * ver + 128) * ver + 64
	if ver >= 2 {
		num_align := ver / 7 + 2
		result -= (25 * num_align - 10) * num_align - 55
		if ver >= 7 {
			result -= 36
		}
	}
	assert(208 <= result && result <= 29648, "raw data modules out of expected range")
	return result
}

@(private)
reed_solomon_compute_divisor :: proc(degree: int, result: []u8) {
	assert(1 <= degree && degree <= REED_SOLOMON_DEGREE_MAX, "reed-solomon degree out of range")
	slice.zero(result[:degree])
	result[degree - 1] = 1

	root: u8 = 1
	for _ in 0 ..< degree {
		for j in 0 ..< degree {
			result[j] = reed_solomon_multiply(result[j], root)
			if j + 1 < degree {
				result[j] ~= result[j + 1]
			}
		}
		root = reed_solomon_multiply(root, 0x02)
	}
}

@(private)
reed_solomon_compute_remainder :: proc(data: []u8, generator: []u8, result: []u8) {
	degree := len(generator)
	assert(1 <= degree && degree <= REED_SOLOMON_DEGREE_MAX, "reed-solomon degree out of range")
	slice.zero(result[:degree])
	for i in 0 ..< len(data) {
		factor := data[i] ~ result[0]
		for j in 0 ..< degree - 1 {
			result[j] = result[j + 1]
		}
		result[degree - 1] = 0
		for j in 0 ..< degree {
			result[j] ~= reed_solomon_multiply(generator[j], factor)
		}
	}
}

// Returns the product of two field elements modulo GF(2^8/0x11D).
@(private)
reed_solomon_multiply :: proc(x, y: u8) -> u8 {
	z: u8 = 0
	for i := int(7); i >= 0; i -= 1 {
		z = u8((uint(z) << 1) ~ ((uint(z) >> 7) * 0x11D))
		z ~= ((y >> uint(i)) & 1) * x
	}
	return z
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Drawing Function Modules ------------------------
// ---------------------------------------------------------------------------------------------------------------------

// Clears the QR Code grid and marks every function module as dark.
@(private)
initialize_function_modules :: proc(version: int, qrcode: []u8) {
	qrsize := version * 4 + 17
	total_bytes := (qrsize * qrsize + 7) / 8 + 1
	slice.zero(qrcode[:total_bytes])
	qrcode[0] = u8(qrsize)

	fill_rectangle(6, 0, 1, qrsize, qrcode)
	fill_rectangle(0, 6, qrsize, 1, qrcode)

	fill_rectangle(0, 0, 9, 9, qrcode)
	fill_rectangle(qrsize - 8, 0, 8, 9, qrcode)
	fill_rectangle(0, qrsize - 8, 9, 8, qrcode)

	align_pat_pos: [7]u8
	num_align := get_alignment_pattern_positions(version, &align_pat_pos)
	for i in 0 ..< num_align {
		for j in 0 ..< num_align {
			if !((i == 0 && j == 0) || (i == 0 && j == num_align - 1) || (i == num_align - 1 && j == 0)) {
				fill_rectangle(int(align_pat_pos[i]) - 2, int(align_pat_pos[j]) - 2, 5, 5, qrcode)
			}
		}
	}

	if version >= 7 {
		fill_rectangle(qrsize - 11, 0, 3, 6, qrcode)
		fill_rectangle(0, qrsize - 11, 6, 3, qrcode)
	}
}

@(private)
draw_light_function_modules :: proc(qrcode: []u8, version: int) {
	qrsize := get_size(qrcode)

	for i := 7; i < qrsize - 7; i += 2 {
		set_module_bounded(qrcode, 6, i, false)
		set_module_bounded(qrcode, i, 6, false)
	}

	for dy in -4 ..= 4 {
		for dx in -4 ..= 4 {
			dist := abs(dx)
			if abs(dy) > dist {
				dist = abs(dy)
			}
			if dist == 2 || dist == 4 {
				set_module_unbounded(qrcode, 3 + dx, 3 + dy, false)
				set_module_unbounded(qrcode, qrsize - 4 + dx, 3 + dy, false)
				set_module_unbounded(qrcode, 3 + dx, qrsize - 4 + dy, false)
			}
		}
	}

	align_pat_pos: [7]u8
	num_align := get_alignment_pattern_positions(version, &align_pat_pos)
	for i in 0 ..< num_align {
		for j in 0 ..< num_align {
			if (i == 0 && j == 0) || (i == 0 && j == num_align - 1) || (i == num_align - 1 && j == 0) {
				continue
			}
			for dy in -1 ..= 1 {
				for dx in -1 ..= 1 {
					set_module_bounded(qrcode, int(align_pat_pos[i]) + dx, int(align_pat_pos[j]) + dy, dx == 0 && dy == 0)
				}
			}
		}
	}

	if version >= 7 {
		rem := version
		for _ in 0 ..< 12 {
			rem = (rem << 1) ~ ((rem >> 11) * 0x1F25)
		}
		bits := version << 12 | rem
		assert(bits >> 18 == 0, "version bits overflow")

		for i in 0 ..< 6 {
			for j in 0 ..< 3 {
				k := qrsize - 11 + j
				set_module_bounded(qrcode, k, i, (bits & 1) != 0)
				set_module_bounded(qrcode, i, k, (bits & 1) != 0)
				bits >>= 1
			}
		}
	}
}

@(private)
draw_format_bits :: proc(ecl: Ecc, mask: Mask, qrcode: []u8) {
	format_table := [4]int{1, 0, 3, 2}
	data := format_table[int(ecl)] << 3 | int(mask)
	rem := data
	for _ in 0 ..< 10 {
		rem = (rem << 1) ~ ((rem >> 9) * 0x537)
	}
	bits := (data << 10 | rem) ~ 0x5412
	assert(bits >> 15 == 0, "format bits overflow")

	for i in 0 ..= 5 {
		set_module_bounded(qrcode, 8, i, get_bit(bits, uint(i)))
	}
	set_module_bounded(qrcode, 8, 7, get_bit(bits, 6))
	set_module_bounded(qrcode, 8, 8, get_bit(bits, 7))
	set_module_bounded(qrcode, 7, 8, get_bit(bits, 8))
	for i in 9 ..< 15 {
		set_module_bounded(qrcode, 14 - i, 8, get_bit(bits, uint(i)))
	}

	qrsize := get_size(qrcode)
	for i in 0 ..< 8 {
		set_module_bounded(qrcode, qrsize - 1 - i, 8, get_bit(bits, uint(i)))
	}
	for i in 8 ..< 15 {
		set_module_bounded(qrcode, 8, qrsize - 15 + i, get_bit(bits, uint(i)))
	}
	set_module_bounded(qrcode, 8, qrsize - 8, true)
}

@(private)
get_alignment_pattern_positions :: proc(version: int, result: ^[7]u8) -> int {
	if version == 1 {
		return 0
	}
	num_align := version / 7 + 2
	step := (version * 8 + num_align * 3 + 5) / (num_align * 4 - 4) * 2
	pos := version * 4 + 10
	for i := num_align - 1; i >= 1; i -= 1 {
		result[i] = u8(pos)
		pos -= step
	}
	result[0] = 6
	return num_align
}

@(private)
fill_rectangle :: proc(left, top, width, height: int, qrcode: []u8) {
	for dy in 0 ..< height {
		for dx in 0 ..< width {
			set_module_bounded(qrcode, left + dx, top + dy, true)
		}
	}
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Drawing data modules and masking ------------------------
// ---------------------------------------------------------------------------------------------------------------------

@(private)
draw_codewords :: proc(data: []u8, data_len: int, qrcode: []u8) {
	qrsize := get_size(qrcode)
	i := 0
	right := qrsize - 1
	for right >= 1 {
		if right == 6 {
			right = 5
		}
		for vert in 0 ..< qrsize {
			for j in 0 ..< 2 {
				x := right - j
				upward := ((right + 1) & 2) == 0
				y := (qrsize - 1 - vert) if upward else vert
				if !get_module_bounded(qrcode, x, y) && i < data_len * 8 {
					dark := get_bit(int(data[i >> 3]), uint(7 - (i & 7)))
					set_module_bounded(qrcode, x, y, dark)
					i += 1
				}
			}
		}
		right -= 2
	}
	assert(i == data_len * 8, "not all codeword bits were consumed")
}

@(private)
apply_mask :: proc(function_modules: []u8, qrcode: []u8, mask: Mask) {
	qrsize := get_size(qrcode)
	for y in 0 ..< qrsize {
		for x in 0 ..< qrsize {
			if get_module_bounded(function_modules, x, y) {
				continue
			}
			invert: bool
			switch mask {
			case .M0: invert = (x + y) % 2 == 0
			case .M1: invert = y % 2 == 0
			case .M2: invert = x % 3 == 0
			case .M3: invert = (x + y) % 3 == 0
			case .M4: invert = (x / 3 + y / 2) % 2 == 0
			case .M5: invert = x * y % 2 + x * y % 3 == 0
			case .M6: invert = (x * y % 2 + x * y % 3) % 2 == 0
			case .M7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0
			}
			val := get_module_bounded(qrcode, x, y)
			set_module_bounded(qrcode, x, y, val ~ invert) // XOR booleans
		}
	}
}

@(private)
get_penalty_score :: proc(qrcode: []u8) -> int {
	qrsize := get_size(qrcode)
	result := 0

	// Adjacent modules in row having same color, and finder-like patterns
	for y in 0 ..< qrsize {
		run_color := false
		run_x := 0
		run_history: [7]int
		for x in 0 ..< qrsize {
			if get_module_bounded(qrcode, x, y) == run_color {
				run_x += 1
				if run_x == 5 {
					result += PENALTY_N1
				} else if run_x > 5 {
					result += 1
				}
			} else {
				finder_penalty_add_history(run_x, &run_history, qrsize)
				if !run_color {
					result += finder_penalty_count_patterns(&run_history, qrsize) * PENALTY_N3
				}
				run_color = get_module_bounded(qrcode, x, y)
				run_x = 1
			}
		}
		result += finder_penalty_terminate_and_count(run_color, run_x, &run_history, qrsize) * PENALTY_N3
	}

	// Adjacent modules in column having same color, and finder-like patterns
	for x in 0 ..< qrsize {
		run_color := false
		run_y := 0
		run_history: [7]int
		for y in 0 ..< qrsize {
			if get_module_bounded(qrcode, x, y) == run_color {
				run_y += 1
				if run_y == 5 {
					result += PENALTY_N1
				} else if run_y > 5 {
					result += 1
				}
			} else {
				finder_penalty_add_history(run_y, &run_history, qrsize)
				if !run_color {
					result += finder_penalty_count_patterns(&run_history, qrsize) * PENALTY_N3
				}
				run_color = get_module_bounded(qrcode, x, y)
				run_y = 1
			}
		}
		result += finder_penalty_terminate_and_count(run_color, run_y, &run_history, qrsize) * PENALTY_N3
	}

	// 2*2 blocks of modules having same color
	for y in 0 ..< qrsize - 1 {
		for x in 0 ..< qrsize - 1 {
			color := get_module_bounded(qrcode, x, y)
			if color == get_module_bounded(qrcode, x + 1, y) &&
			   color == get_module_bounded(qrcode, x, y + 1) &&
			   color == get_module_bounded(qrcode, x + 1, y + 1) {
				result += PENALTY_N2
			}
		}
	}

	// Balance of dark and light modules
	dark := 0
	for y in 0 ..< qrsize {
		for x in 0 ..< qrsize {
			if get_module_bounded(qrcode, x, y) {
				dark += 1
			}
		}
	}
	total := qrsize * qrsize
	k := (abs(dark * 20 - total * 10) + total - 1) / total - 1
	assert(0 <= k && k <= 9, "penalty balance factor out of range")
	result += k * PENALTY_N4
	return result
}

@(private)
finder_penalty_count_patterns :: proc(run_history: ^[7]int, qrsize: int) -> int {
	n := run_history[1]
	assert(n <= qrsize * 3, "finder penalty run exceeds limit")
	core :=
		n > 0 && run_history[2] == n && run_history[3] == n * 3 && run_history[4] == n && run_history[5] == n
	return(
		(1 if core && run_history[0] >= n * 4 && run_history[6] >= n else 0) +
		(1 if core && run_history[6] >= n * 4 && run_history[0] >= n else 0) \
	)
}

@(private)
finder_penalty_terminate_and_count :: proc(
	current_run_color: bool,
	current_run_length: int,
	run_history: ^[7]int,
	qrsize: int,
) -> int {
	current_run_length := current_run_length
	if current_run_color {
		finder_penalty_add_history(current_run_length, run_history, qrsize)
		current_run_length = 0
	}
	current_run_length += qrsize
	finder_penalty_add_history(current_run_length, run_history, qrsize)
	return finder_penalty_count_patterns(run_history, qrsize)
}

@(private)
finder_penalty_add_history :: proc(current_run_length: int, run_history: ^[7]int, qrsize: int) {
	current_run_length := current_run_length
	if run_history[0] == 0 {
		current_run_length += qrsize
	}
	// Shift history right by 1
	for i := 5; i >= 0; i -= 1 {
		run_history[i + 1] = run_history[i]
	}
	run_history[0] = current_run_length
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Basic QR code information ------------------------
// ---------------------------------------------------------------------------------------------------------------------

// Returns the minimum buffer size (in bytes) needed for both temp_buffer and qrcode
// to encode the given content at the given ECC level within the given version range.
//
// Returns ok=false when:
//   - The content exceeds QR Code capacity for every version in [min_version, max_version]
//     at the given ECL.
min_buffer_size :: proc {
	min_buffer_size_text,
	min_buffer_size_binary,
	min_buffer_size_segments,
}

// Text path: auto-selects numeric/alphanumeric/byte mode the same way encode_text_manual does.
//
// Returns ok=false when:
//   - The text exceeds QR Code capacity for every version in the range at the given ECL.
//   - The encoded segment data would exceed the QR grid buffer size (theoretical edge case).
@(require_results)
min_buffer_size_text :: proc(
	text: string,
	ecl: Ecc,
	min_version: int = VERSION_MIN,
	max_version: int = VERSION_MAX,
) -> (
	size: int,
	ok: bool,
) {
	text_len := len(text)
	if text_len == 0 {
		return buffer_len_for_version(min_version), true
	}

	mode: Mode = .Byte
	if is_numeric(text) {
		mode = .Numeric
	} else if is_alphanumeric(text) {
		mode = .Alphanumeric
	}

	bit_len := calc_segment_bit_length(mode, text_len)
	if bit_len == LENGTH_OVERFLOW do return 0, false

	for v in min_version ..= max_version {
		total := 4 + num_char_count_bits(mode, v) + bit_len
		if total <= get_num_data_codewords(v, ecl) * 8 {
			buf_size := buffer_len_for_version(v)
			// Segment data is written into temp_buffer, so it must also fit the segment buffer.
			seg_buf := calc_segment_buffer_size(mode, text_len)
			if seg_buf < 0 || seg_buf > buf_size do return 0, false
			return buf_size, true
		}
	}
	return 0, false
}

// Binary path: always byte mode, only needs the payload length.
//
// Returns ok=false when:
//   - The payload cannot fit in any version within [min_version, max_version] at the given ECL.
@(require_results)
min_buffer_size_binary :: proc(
	data_len: int,
	ecl: Ecc,
	min_version: int = VERSION_MIN,
	max_version: int = VERSION_MAX,
) -> (
	size: int,
	ok: bool,
) {
	if data_len == 0 {
		return buffer_len_for_version(min_version), true
	}

	bit_len := calc_segment_bit_length(.Byte, data_len)
	if bit_len == LENGTH_OVERFLOW do return 0, false

	for v in min_version ..= max_version {
		total := 4 + num_char_count_bits(.Byte, v) + bit_len
		if total <= get_num_data_codewords(v, ecl) * 8 {
			return buffer_len_for_version(v), true
		}
	}
	return 0, false
}

// Segments path: sums per-segment bit costs across candidate versions.
//
// Returns ok=false when:
//   - The combined segment data exceeds QR Code capacity for every version in the range
//     at the given ECL.
@(require_results)
min_buffer_size_segments :: proc(
	segs: []Segment,
	ecl: Ecc,
	min_version: int = VERSION_MIN,
	max_version: int = VERSION_MAX,
) -> (
	size: int,
	ok: bool,
) {
	if len(segs) == 0 {
		return buffer_len_for_version(min_version), true
	}

	for v in min_version ..= max_version {
		data_used_bits := get_total_bits(segs, v)
		if data_used_bits != LENGTH_OVERFLOW && data_used_bits <= get_num_data_codewords(v, ecl) * 8 {
			return buffer_len_for_version(v), true
		}
	}
	return 0, false
}

// Returns the side length of the given QR Code (range [21, 177]).
get_size :: #force_inline proc(qrcode: []u8) -> int {
	return int(qrcode[0])
}

// Returns the color of the module at the given coordinates (false=light, true=dark).
// Out-of-bounds coordinates return false.
get_module :: proc(qrcode: []u8, x, y: int) -> bool {
	qrsize := int(qrcode[0])
	return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && get_module_bounded(qrcode, x, y)
}

@(private)
get_module_bounded :: #force_inline proc(qrcode: []u8, x, y: int) -> bool {
	qrsize := int(qrcode[0])
	index := y * qrsize + x
	return get_bit(int(qrcode[(index >> 3) + 1]), uint(index & 7))
}

@(private)
set_module_bounded :: #force_inline proc(qrcode: []u8, x, y: int, is_dark: bool) {
	qrsize := int(qrcode[0])
	index := y * qrsize + x
	bit_index := uint(index & 7)
	byte_index := (index >> 3) + 1
	if is_dark {
		qrcode[byte_index] |= u8(1) << bit_index
	} else {
		qrcode[byte_index] &= ~(u8(1) << bit_index)
	}
}

@(private)
set_module_unbounded :: #force_inline proc(qrcode: []u8, x, y: int, is_dark: bool) {
	qrsize := int(qrcode[0])
	if 0 <= x && x < qrsize && 0 <= y && y < qrsize {
		set_module_bounded(qrcode, x, y, is_dark)
	}
}

@(private)
get_bit :: #force_inline proc(x: int, i: uint) -> bool {
	return ((x >> i) & 1) != 0
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Segment Handling ------------------------
// ---------------------------------------------------------------------------------------------------------------------

// Tests whether the given string can be encoded in numeric mode.
is_numeric :: proc(text: string) -> bool {
	for i in 0 ..< len(text) {
		if text[i] < '0' || text[i] > '9' {
			return false
		}
	}
	return true
}

// Tests whether the given string can be encoded in alphanumeric mode.
// Valid characters: 0-9, A-Z (uppercase), space, $ % * + - . / :
is_alphanumeric :: proc(text: string) -> bool {
	for i in 0 ..< len(text) {
		if alphanumeric_index(text[i]) < 0 do return false
	}
	return true
}

// Returns the number of bytes needed for the data buffer of a segment.
// Returns -1 when num_chars exceeds the representable range for the given mode
// (i.e. the resulting bit length would exceed 32767).
calc_segment_buffer_size :: proc(mode: Mode, num_chars: int) -> int {
	temp := calc_segment_bit_length(mode, num_chars)
	if temp == LENGTH_OVERFLOW {
		return -1
	}
	assert(0 <= temp && temp <= 32767, "segment bit length out of range")
	return (temp + 7) / 8
}

calc_segment_bit_length :: proc(mode: Mode, num_chars: int) -> int {
	if num_chars < 0 || num_chars > 32767 {
		return LENGTH_OVERFLOW
	}
	result := num_chars
	switch mode {
	case .Numeric: result = (result * 10 + 2) / 3
	case .Alphanumeric: result = (result * 11 + 1) / 2
	case .Byte: result *= 8
	case .Kanji: result *= 13
	case .Eci:
		assert(num_chars == 0, "ECI mode requires num_chars == 0")
		result = 3 * 8
	}
	assert(result >= 0, "negative bit length")
	if result > 32767 {
		return LENGTH_OVERFLOW
	}
	return result
}

// Returns a segment representing the given binary data encoded in byte mode.
//
// Parameters:
//   data - [in]  Source bytes to encode. May be nil/empty.
//   buf  - [out] Buffer to receive the copied data. Must have length >= len(data).
//                The segment's data pointer will reference this buffer.
make_bytes :: proc(data: []u8, buf: []u8) -> Segment {
	result: Segment
	result.mode = .Byte
	result.bit_length = calc_segment_bit_length(.Byte, len(data))
	assert(result.bit_length != LENGTH_OVERFLOW, "segment data too long")
	result.num_chars = len(data)
	if len(data) > 0 {
		copy(buf, data)
	}
	result.data = buf[:len(data)]
	return result
}

// Returns a segment representing the given decimal digits encoded in numeric mode.
// Every character in digits must be '0'-'9'.
//
// Parameters:
//   digits - [in]  String of decimal digit characters.
//   buf    - [out]  Buffer to receive the packed bit data. Must have length >=
//                  calc_segment_buffer_size(.Numeric, len(digits)). The segment's data
//                  pointer will reference this buffer.
make_numeric :: proc(digits: string, buf: []u8) -> Segment {
	result: Segment
	digit_len := len(digits)
	result.mode = .Numeric
	bit_len := calc_segment_bit_length(.Numeric, digit_len)
	assert(bit_len != LENGTH_OVERFLOW, "segment data too long")
	result.num_chars = digit_len
	bytes_needed := (bit_len + 7) / 8
	if bit_len > 0 {
		slice.zero(buf[:bytes_needed])
	}
	result.bit_length = 0

	accum_data: uint = 0
	accum_count := 0
	for i in 0 ..< digit_len {
		c := digits[i]
		assert('0' <= c && c <= '9', "non-digit character in numeric segment")
		accum_data = accum_data * 10 + uint(c - '0')
		accum_count += 1
		if accum_count == 3 {
			append_bits_to_buffer(accum_data, 10, buf, &result.bit_length)
			accum_data = 0
			accum_count = 0
		}
	}
	if accum_count > 0 {
		append_bits_to_buffer(accum_data, accum_count * 3 + 1, buf, &result.bit_length)
	}
	assert(result.bit_length == bit_len, "encoded bit length mismatch")
	result.data = buf[:bytes_needed]
	return result
}

// Returns a segment representing the given text encoded in alphanumeric mode.
// Every character must be in: 0-9 A-Z (uppercase) space $ % * + - . / :
//
// Parameters:
//   text - [in]  String of alphanumeric characters.
//   buf  - [out] Buffer to receive the packed bit data. Must have length >=
//                calc_segment_buffer_size(.Alphanumeric, len(text)). The segment's data
//                pointer will reference this buffer.
make_alphanumeric :: proc(text: string, buf: []u8) -> Segment {
	result: Segment
	text_len := len(text)
	result.mode = .Alphanumeric
	bit_len := calc_segment_bit_length(.Alphanumeric, text_len)
	assert(bit_len != LENGTH_OVERFLOW, "segment data too long")
	result.num_chars = text_len
	bytes_needed := (bit_len + 7) / 8
	if bit_len > 0 {
		slice.zero(buf[:bytes_needed])
	}
	result.bit_length = 0

	accum_data: uint = 0
	accum_count := 0
	for i in 0 ..< text_len {
		idx := alphanumeric_index(text[i])
		assert(idx >= 0, "character not in alphanumeric charset")
		accum_data = accum_data * 45 + uint(idx)
		accum_count += 1
		if accum_count == 2 {
			append_bits_to_buffer(accum_data, 11, buf, &result.bit_length)
			accum_data = 0
			accum_count = 0
		}
	}
	if accum_count > 0 {
		append_bits_to_buffer(accum_data, 6, buf, &result.bit_length)
	}
	assert(result.bit_length == bit_len, "encoded bit length mismatch")
	result.data = buf[:bytes_needed]
	return result
}

// Returns a segment representing an Extended Channel Interpretation (ECI) designator
// with the given assignment value (must be in [0, 999999]).
//
// Parameters:
//   buf - [out] Buffer to receive the packed ECI data. Must have length >= 3.
//               The segment's data pointer will reference this buffer.
make_eci :: proc(assign_val: int, buf: []u8) -> Segment {
	result: Segment
	result.mode = .Eci
	result.num_chars = 0
	result.bit_length = 0
	if assign_val < 0 {
		assert(false, "ECI assignment value must be non-negative")
	} else if assign_val < (1 << 7) {
		slice.zero(buf[:1])
		append_bits_to_buffer(uint(assign_val), 8, buf, &result.bit_length)
	} else if assign_val < (1 << 14) {
		slice.zero(buf[:2])
		append_bits_to_buffer(2, 2, buf, &result.bit_length)
		append_bits_to_buffer(uint(assign_val), 14, buf, &result.bit_length)
	} else if assign_val < 1000000 {
		slice.zero(buf[:3])
		append_bits_to_buffer(6, 3, buf, &result.bit_length)
		append_bits_to_buffer(uint(assign_val >> 10), 11, buf, &result.bit_length)
		append_bits_to_buffer(uint(assign_val & 0x3FF), 10, buf, &result.bit_length)
	} else {
		assert(false, "ECI assignment value must be < 1000000")
	}
	result.data = buf[:(result.bit_length + 7) / 8]
	return result
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Helpers ------------------------
// ---------------------------------------------------------------------------------------------------------------------

// Internal
append_bits_to_buffer :: proc(val: uint, num_bits: int, buffer: []u8, bit_len: ^int) {
	assert(0 <= num_bits && num_bits <= 16 && val >> uint(num_bits) == 0, "invalid bit count or value overflow")
	for i := num_bits - 1; i >= 0; i -= 1 {
		buffer[bit_len^ >> 3] |= u8(((val >> uint(i)) & 1)) << uint(7 - (bit_len^ & 7))
		bit_len^ += 1
	}
}

// Internal
get_total_bits :: proc(segs: []Segment, version: int) -> int {
	result := 0
	for &seg in segs {
		num_chars := seg.num_chars
		bit_length := seg.bit_length
		assert(0 <= num_chars && num_chars <= 32767, "segment num_chars out of range")
		assert(0 <= bit_length && bit_length <= 32767, "segment bit_length out of range")
		ccbits := num_char_count_bits(seg.mode, version)
		assert(0 <= ccbits && ccbits <= 16, "char count bit width out of range")
		if num_chars >= (1 << uint(ccbits)) {
			return LENGTH_OVERFLOW
		}
		result += 4 + ccbits + bit_length
		if result > 32767 {
			return LENGTH_OVERFLOW
		}
	}
	assert(0 <= result && result <= 32767, "total bits out of range")
	return result
}

// Internal
num_char_count_bits :: proc(mode: Mode, version: int) -> int {
	assert(VERSION_MIN <= version && version <= VERSION_MAX, "version out of bounds")
	i := (version + 7) / 17
	switch mode {
	case .Numeric:
		t := [3]int{10, 12, 14}
		return t[i]
	case .Alphanumeric:
		t := [3]int{9, 11, 13}
		return t[i]
	case .Byte:
		t := [3]int{8, 16, 16}
		return t[i]
	case .Kanji:
		t := [3]int{8, 10, 12}
		return t[i]
	case .Eci: return 0
	}
	unreachable()
}

// Internal
// Returns the index of c in the alphanumeric charset (0-44), or -1 if not found.
alphanumeric_index :: proc(c: u8) -> int {
	switch c {
	case '0' ..= '9': return int(c - '0')
	case 'A' ..= 'Z': return int(c - 'A') + 10
	case ' ': return 36
	case '$': return 37
	case '%': return 38
	case '*': return 39
	case '+': return 40
	case '-': return 41
	case '.': return 42
	case '/': return 43
	case ':': return 44
	}
	return -1
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Tests ------------------------
// ---------------------------------------------------------------------------------------------------------------------
import "core:math/rand"
import "core:testing"

// Helper: reference implementation of add_ecc_and_interleave for testing.
// This uses a straightforward block-by-block approach with dynamic allocation,
// then interleaves column-by-column. The result is written to the provided buffer.
add_ecc_and_interleave_reference :: proc(data: []u8, version: int, ecl: Ecc, result: []u8) {
	num_blocks := int(NUM_ERROR_CORRECTION_BLOCKS[int(ecl)][version])
	block_ecc_len := int(ECC_CODEWORDS_PER_BLOCK[int(ecl)][version])
	raw_codewords := get_num_raw_data_modules(version) / 8
	num_short_blocks := num_blocks - raw_codewords % num_blocks
	short_block_data_len := raw_codewords / num_blocks - block_ecc_len

	gen: [REED_SOLOMON_DEGREE_MAX]u8
	reed_solomon_compute_divisor(block_ecc_len, gen[:block_ecc_len])

	blocks := make([][]u8, num_blocks)
	defer {
		for &b in blocks {
			delete(b)
		}
		delete(blocks)
	}

	k := 0
	for i in 0 ..< num_blocks {
		dat_len := short_block_data_len + (0 if i < num_short_blocks else 1)
		block_len := dat_len + block_ecc_len
		blocks[i] = make([]u8, block_len)
		copy(blocks[i][:dat_len], data[k:k + dat_len])
		reed_solomon_compute_remainder(
			data[k:k + dat_len],
			gen[:block_ecc_len],
			blocks[i][dat_len:dat_len + block_ecc_len],
		)
		k += dat_len
	}

	k = 0
	for i in 0 ..= short_block_data_len {
		for j in 0 ..< num_blocks {
			if i == short_block_data_len && j < num_short_blocks {
				continue
			}
			result[k] = blocks[j][i]
			k += 1
		}
	}
	for i in 0 ..< block_ecc_len {
		for j in 0 ..< num_blocks {
			dat_len := short_block_data_len + (0 if j < num_short_blocks else 1)
			result[k] = blocks[j][dat_len + i]
			k += 1
		}
	}
}

@(test)
test_append_bits_to_buffer :: proc(t: ^testing.T) {
	// Sub-test 1: single byte buffer
	{
		buf: [1]u8
		bit_len := 0
		append_bits_to_buffer(0, 0, buf[:], &bit_len)
		testing.expect_value(t, bit_len, 0)
		testing.expect_value(t, buf[0], u8(0x00))
		append_bits_to_buffer(1, 1, buf[:], &bit_len)
		testing.expect_value(t, bit_len, 1)
		testing.expect_value(t, buf[0], u8(0x80))
		append_bits_to_buffer(0, 1, buf[:], &bit_len)
		testing.expect_value(t, bit_len, 2)
		testing.expect_value(t, buf[0], u8(0x80))
		append_bits_to_buffer(5, 3, buf[:], &bit_len)
		testing.expect_value(t, bit_len, 5)
		testing.expect_value(t, buf[0], u8(0xA8))
		append_bits_to_buffer(6, 3, buf[:], &bit_len)
		testing.expect_value(t, bit_len, 8)
		testing.expect_value(t, buf[0], u8(0xAE))
	}
	// Sub-test 2: multi-byte buffer
	{
		buf: [6]u8
		bit_len := 0
		append_bits_to_buffer(16942, 16, buf[:], &bit_len)
		testing.expect_value(t, bit_len, 16)
		testing.expect_value(t, buf[0], u8(0x42))
		testing.expect_value(t, buf[1], u8(0x2E))
		testing.expect_value(t, buf[2], u8(0x00))
		testing.expect_value(t, buf[3], u8(0x00))
		testing.expect_value(t, buf[4], u8(0x00))
		testing.expect_value(t, buf[5], u8(0x00))
		append_bits_to_buffer(10, 7, buf[:], &bit_len)
		testing.expect_value(t, bit_len, 23)
		testing.expect_value(t, buf[0], u8(0x42))
		testing.expect_value(t, buf[1], u8(0x2E))
		testing.expect_value(t, buf[2], u8(0x14))
		testing.expect_value(t, buf[3], u8(0x00))
		testing.expect_value(t, buf[4], u8(0x00))
		testing.expect_value(t, buf[5], u8(0x00))
		append_bits_to_buffer(15, 4, buf[:], &bit_len)
		testing.expect_value(t, bit_len, 27)
		testing.expect_value(t, buf[0], u8(0x42))
		testing.expect_value(t, buf[1], u8(0x2E))
		testing.expect_value(t, buf[2], u8(0x15))
		testing.expect_value(t, buf[3], u8(0xE0))
		testing.expect_value(t, buf[4], u8(0x00))
		testing.expect_value(t, buf[5], u8(0x00))
		append_bits_to_buffer(26664, 15, buf[:], &bit_len)
		testing.expect_value(t, bit_len, 42)
		testing.expect_value(t, buf[0], u8(0x42))
		testing.expect_value(t, buf[1], u8(0x2E))
		testing.expect_value(t, buf[2], u8(0x15))
		testing.expect_value(t, buf[3], u8(0xFA))
		testing.expect_value(t, buf[4], u8(0x0A))
		testing.expect_value(t, buf[5], u8(0x00))
	}
}

@(test)
test_add_ecc_and_interleave :: proc(t: ^testing.T) {
	for ver in VERSION_MIN ..= VERSION_MAX {
		for ecl_int in 0 ..< 4 {
			ecl := Ecc(ecl_int)
			data_len := get_num_data_codewords(ver, ecl)
			raw_codewords := get_num_raw_data_modules(ver) / 8

			data: [BUFFER_LEN_MAX]u8
			data_copy: [BUFFER_LEN_MAX]u8
			result: [BUFFER_LEN_MAX]u8
			expected: [BUFFER_LEN_MAX]u8

			for i in 0 ..< data_len {
				data[i] = u8(rand.uint32())
			}
			copy(data_copy[:data_len], data[:data_len])

			add_ecc_and_interleave_reference(data_copy[:data_len], ver, ecl, expected[:])
			add_ecc_and_interleave(data[:], ver, ecl, result[:])

			for i in 0 ..< raw_codewords {
				testing.expect_value(t, result[i], expected[i])
			}
		}
	}
}

@(test)
test_get_num_data_codewords :: proc(t: ^testing.T) {
	//odinfmt: disable
	vers := [?]int{3, 3, 3, 6, 7, 9, 9, 12, 15, 16, 19, 21, 22, 22, 22, 24, 24, 28, 30, 32, 33, 33, 35, 35, 35, 36, 37, 39, 40}
	ecls := [?]int{1, 2, 3, 0, 0, 0, 1, 3, 0, 2, 3, 0, 0, 1, 3, 0, 3, 0, 3, 3, 0, 3, 0, 1, 2, 3, 3, 1, 1}
	exps := [?]int{44, 34, 26, 136, 156, 232, 182, 158, 523, 325, 341, 932, 1006, 782, 442, 1174, 514, 1531, 745, 845, 2071, 901, 2306, 1812, 1286, 1054, 1096, 2216, 2334}
	//odinfmt: enable

	for i in 0 ..< len(vers) {
		testing.expect_value(t, get_num_data_codewords(vers[i], Ecc(ecls[i])), exps[i])
	}
}

@(test)
test_get_num_raw_data_modules :: proc(t: ^testing.T) {
	//odinfmt: disable
	vers := [?]int{1, 2, 3, 6, 7, 12, 15, 18, 22, 26, 32, 37, 40}
	exps := [?]int{208, 359, 567, 1383, 1568, 3728, 5243, 7211, 10068, 13652, 19723, 25568, 29648}
	//odinfmt: enable

	for i in 0 ..< len(vers) {
		testing.expect_value(t, get_num_raw_data_modules(vers[i]), exps[i])
	}
}

@(test)
test_reed_solomon_compute_divisor :: proc(t: ^testing.T) {
	// Degree 1
	{
		gen: [1]u8
		reed_solomon_compute_divisor(1, gen[:])
		testing.expect_value(t, gen[0], u8(0x01))
	}
	// Degree 2
	{
		gen: [2]u8
		reed_solomon_compute_divisor(2, gen[:])
		testing.expect_value(t, gen[0], u8(0x03))
		testing.expect_value(t, gen[1], u8(0x02))
	}
	// Degree 5
	{
		gen: [5]u8
		reed_solomon_compute_divisor(5, gen[:])
		testing.expect_value(t, gen[0], u8(0x1F))
		testing.expect_value(t, gen[1], u8(0xC6))
		testing.expect_value(t, gen[2], u8(0x3F))
		testing.expect_value(t, gen[3], u8(0x93))
		testing.expect_value(t, gen[4], u8(0x74))
	}
	// Degree 30
	{
		gen: [REED_SOLOMON_DEGREE_MAX]u8
		reed_solomon_compute_divisor(30, gen[:30])
		testing.expect_value(t, gen[0], u8(0xD4))
		testing.expect_value(t, gen[1], u8(0xF6))
		testing.expect_value(t, gen[5], u8(0xC0))
		testing.expect_value(t, gen[12], u8(0x16))
		testing.expect_value(t, gen[13], u8(0xD9))
		testing.expect_value(t, gen[20], u8(0x12))
		testing.expect_value(t, gen[27], u8(0x6A))
		testing.expect_value(t, gen[29], u8(0x96))
	}
}

@(test)
test_reed_solomon_compute_remainder :: proc(t: ^testing.T) {
	// Sub-test 1: empty data, degree 3 → all zeros
	{
		gen: [3]u8
		reed_solomon_compute_divisor(3, gen[:])
		rem: [3]u8
		empty: [0]u8
		reed_solomon_compute_remainder(empty[:], gen[:], rem[:])
		testing.expect_value(t, rem[0], u8(0x00))
		testing.expect_value(t, rem[1], u8(0x00))
		testing.expect_value(t, rem[2], u8(0x00))
	}
	// Sub-test 2: data={0,1}, degree 4 → remainder == generator
	{
		gen: [4]u8
		reed_solomon_compute_divisor(4, gen[:])
		data := [?]u8{0x00, 0x01}
		rem: [4]u8
		reed_solomon_compute_remainder(data[:], gen[:], rem[:])
		testing.expect_value(t, rem[0], gen[0])
		testing.expect_value(t, rem[1], gen[1])
		testing.expect_value(t, rem[2], gen[2])
		testing.expect_value(t, rem[3], gen[3])
	}
	// Sub-test 3: 5-byte data, degree 5
	{
		gen: [5]u8
		reed_solomon_compute_divisor(5, gen[:])
		data := [?]u8{0x03, 0x3A, 0x60, 0x12, 0xC7}
		rem: [5]u8
		reed_solomon_compute_remainder(data[:], gen[:], rem[:])
		testing.expect_value(t, rem[0], u8(0xCB))
		testing.expect_value(t, rem[1], u8(0x36))
		testing.expect_value(t, rem[2], u8(0x16))
		testing.expect_value(t, rem[3], u8(0xFA))
		testing.expect_value(t, rem[4], u8(0x9D))
	}
	// Sub-test 4: 43-byte data, degree 30
	{
		gen: [REED_SOLOMON_DEGREE_MAX]u8
		reed_solomon_compute_divisor(30, gen[:30])
			//odinfmt: disable
		data := [?]u8{
			0x03, 0x3A, 0x60, 0x12, 0xC7, 0x40, 0x04, 0x00,
			0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
			0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
			0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
			0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
			0xEC, 0x11, 0xEC,
		}
		//odinfmt: enable
		rem: [REED_SOLOMON_DEGREE_MAX]u8
		reed_solomon_compute_remainder(data[:], gen[:30], rem[:30])
		testing.expect_value(t, rem[0], u8(0xFE))
		testing.expect_value(t, rem[1], u8(0x71))
		testing.expect_value(t, rem[2], u8(0xFA))
		testing.expect_value(t, rem[3], u8(0xFE))
		testing.expect_value(t, rem[9], u8(0xB9))
		testing.expect_value(t, rem[10], u8(0xC9))
		testing.expect_value(t, rem[15], u8(0xAC))
		testing.expect_value(t, rem[16], u8(0xFA))
		testing.expect_value(t, rem[19], u8(0x07))
		testing.expect_value(t, rem[20], u8(0x63))
		testing.expect_value(t, rem[25], u8(0xB5))
		testing.expect_value(t, rem[29], u8(0xCB))
	}
}

@(test)
test_reed_solomon_multiply :: proc(t: ^testing.T) {
	//odinfmt: disable
	cases := [?][3]u8{
		{0x00, 0x00, 0x00},
		{0x01, 0x01, 0x01},
		{0x02, 0x02, 0x04},
		{0x00, 0x6E, 0x00},
		{0xB2, 0xDD, 0xE6},
		{0x41, 0x11, 0x25},
		{0xB0, 0x1F, 0x11},
		{0x05, 0x75, 0xBC},
		{0x52, 0xB5, 0xAE},
		{0xA8, 0x20, 0xA4},
		{0x0E, 0x44, 0x9F},
		{0xD4, 0x13, 0xA0},
		{0x31, 0x10, 0x37},
		{0x6C, 0x58, 0xCB},
		{0xB6, 0x75, 0x3E},
		{0xFF, 0xFF, 0xE2},
	}
	//odinfmt: enable
	for tc in cases {
		testing.expect_value(t, reed_solomon_multiply(tc[0], tc[1]), tc[2])
	}
}

@(test)
test_initialize_function_modules :: proc(t: ^testing.T) {
	for ver in VERSION_MIN ..= VERSION_MAX {
		qrcode: [BUFFER_LEN_MAX]u8
		initialize_function_modules(ver, qrcode[:])

		size := ver * 4 + 17
		testing.expect_value(t, get_size(qrcode[:]), size)

		has_light := false
		has_dark := false
		for y in 0 ..< size {
			for x in 0 ..< size {
				if get_module(qrcode[:], x, y) {
					has_dark = true
				} else {
					has_light = true
				}
			}
		}
		testing.expect(t, has_light)
		testing.expect(t, has_dark)
	}
}

@(test)
test_get_alignment_pattern_positions :: proc(t: ^testing.T) {
	// {version, num, positions...}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(1, &result)
		testing.expect_value(t, num, 0)
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(2, &result)
		testing.expect_value(t, num, 2)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(18))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(3, &result)
		testing.expect_value(t, num, 2)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(22))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(6, &result)
		testing.expect_value(t, num, 2)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(34))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(7, &result)
		testing.expect_value(t, num, 3)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(22))
		testing.expect_value(t, result[2], u8(38))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(8, &result)
		testing.expect_value(t, num, 3)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(24))
		testing.expect_value(t, result[2], u8(42))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(16, &result)
		testing.expect_value(t, num, 4)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(26))
		testing.expect_value(t, result[2], u8(50))
		testing.expect_value(t, result[3], u8(74))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(25, &result)
		testing.expect_value(t, num, 5)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(32))
		testing.expect_value(t, result[2], u8(58))
		testing.expect_value(t, result[3], u8(84))
		testing.expect_value(t, result[4], u8(110))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(32, &result)
		testing.expect_value(t, num, 6)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(34))
		testing.expect_value(t, result[2], u8(60))
		testing.expect_value(t, result[3], u8(86))
		testing.expect_value(t, result[4], u8(112))
		testing.expect_value(t, result[5], u8(138))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(33, &result)
		testing.expect_value(t, num, 6)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(30))
		testing.expect_value(t, result[2], u8(58))
		testing.expect_value(t, result[3], u8(86))
		testing.expect_value(t, result[4], u8(114))
		testing.expect_value(t, result[5], u8(142))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(39, &result)
		testing.expect_value(t, num, 7)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(26))
		testing.expect_value(t, result[2], u8(54))
		testing.expect_value(t, result[3], u8(82))
		testing.expect_value(t, result[4], u8(110))
		testing.expect_value(t, result[5], u8(138))
		testing.expect_value(t, result[6], u8(166))
	}
	{
		result: [7]u8
		num := get_alignment_pattern_positions(40, &result)
		testing.expect_value(t, num, 7)
		testing.expect_value(t, result[0], u8(6))
		testing.expect_value(t, result[1], u8(30))
		testing.expect_value(t, result[2], u8(58))
		testing.expect_value(t, result[3], u8(86))
		testing.expect_value(t, result[4], u8(114))
		testing.expect_value(t, result[5], u8(142))
		testing.expect_value(t, result[6], u8(170))
	}
}

@(test)
test_get_set_module :: proc(t: ^testing.T) {
	qrcode: [BUFFER_LEN_MAX]u8
	initialize_function_modules(23, qrcode[:])
	size := 23 * 4 + 17 // 109

	// Clear all modules to light
	for y in 0 ..< size {
		for x in 0 ..< size {
			set_module_bounded(qrcode[:], x, y, false)
		}
	}
	// Check all are light
	for y in 0 ..< size {
		for x in 0 ..< size {
			testing.expect(t, !get_module(qrcode[:], x, y))
		}
	}

	// Set all modules to dark
	for y in 0 ..< size {
		for x in 0 ..< size {
			set_module_bounded(qrcode[:], x, y, true)
		}
	}
	// Check all are dark
	for y in 0 ..< size {
		for x in 0 ..< size {
			testing.expect(t, get_module(qrcode[:], x, y))
		}
	}

	// Set out-of-bounds to light (should not change anything)
	set_module_unbounded(qrcode[:], -1, -1, false)
	set_module_unbounded(qrcode[:], -1, 0, false)
	set_module_unbounded(qrcode[:], 0, -1, false)
	set_module_unbounded(qrcode[:], size, 5, false)
	set_module_unbounded(qrcode[:], 72, size, false)
	// Check all still dark
	for y in 0 ..< size {
		for x in 0 ..< size {
			testing.expect(t, get_module(qrcode[:], x, y))
		}
	}

	// Set specific modules to light, verify pattern
	set_module_bounded(qrcode[:], 3, 8, false)
	set_module_bounded(qrcode[:], 61, 49, false)
	testing.expect(t, !get_module(qrcode[:], 3, 8))
	testing.expect(t, get_module(qrcode[:], 2, 8))
	testing.expect(t, get_module(qrcode[:], 4, 8))
	testing.expect(t, !get_module(qrcode[:], 61, 49))
	testing.expect(t, get_module(qrcode[:], 60, 49))
	testing.expect(t, get_module(qrcode[:], 62, 49))
}

@(test)
test_get_set_module_randomly :: proc(t: ^testing.T) {
	qrcode: [BUFFER_LEN_MAX]u8
	initialize_function_modules(1, qrcode[:])
	size :: 21 // version 1: 1*4+17

	modules: [size][size]bool
	for y in 0 ..< size {
		for x in 0 ..< size {
			modules[y][x] = get_module(qrcode[:], x, y)
		}
	}

	for _ in 0 ..< 100000 {
		x := int(rand.uint32() % u32(size * 2)) - size / 2
		y := int(rand.uint32() % u32(size * 2)) - size / 2
		is_in_bounds := 0 <= x && x < size && 0 <= y && y < size
		expected: bool
		if is_in_bounds {
			expected = modules[y][x]
		}
		testing.expect(t, get_module(qrcode[:], x, y) == expected)
		if is_in_bounds {
			new_val := rand.uint32() % 2 == 0
			set_module_bounded(qrcode[:], x, y, new_val)
			modules[y][x] = new_val
		}
	}
}

@(test)
test_is_alphanumeric :: proc(t: ^testing.T) {
	testing.expect(t, is_alphanumeric(""))
	testing.expect(t, is_alphanumeric("0"))
	testing.expect(t, is_alphanumeric("A"))
	testing.expect(t, !is_alphanumeric("a"))
	testing.expect(t, is_alphanumeric(" "))
	testing.expect(t, is_alphanumeric("."))
	testing.expect(t, is_alphanumeric("*"))
	testing.expect(t, !is_alphanumeric(","))
	testing.expect(t, !is_alphanumeric("|"))
	testing.expect(t, !is_alphanumeric("@"))
	testing.expect(t, is_alphanumeric("XYZ"))
	testing.expect(t, !is_alphanumeric("XYZ!"))
	testing.expect(t, is_alphanumeric("79068"))
	testing.expect(t, is_alphanumeric("+123 ABC$"))
	{
		b := [1]u8{0x01}
		testing.expect(t, !is_alphanumeric(string(b[:])))
	}
	{
		b := [1]u8{0x7F}
		testing.expect(t, !is_alphanumeric(string(b[:])))
	}
	{
		b := [1]u8{0x80}
		testing.expect(t, !is_alphanumeric(string(b[:])))
	}
	{
		b := [1]u8{0xC0}
		testing.expect(t, !is_alphanumeric(string(b[:])))
	}
	{
		b := [1]u8{0xFF}
		testing.expect(t, !is_alphanumeric(string(b[:])))
	}
}

@(test)
test_is_numeric :: proc(t: ^testing.T) {
	testing.expect(t, is_numeric(""))
	testing.expect(t, is_numeric("0"))
	testing.expect(t, !is_numeric("A"))
	testing.expect(t, !is_numeric("a"))
	testing.expect(t, !is_numeric(" "))
	testing.expect(t, !is_numeric("."))
	testing.expect(t, !is_numeric("*"))
	testing.expect(t, !is_numeric(","))
	testing.expect(t, !is_numeric("|"))
	testing.expect(t, !is_numeric("@"))
	testing.expect(t, !is_numeric("XYZ"))
	testing.expect(t, !is_numeric("XYZ!"))
	testing.expect(t, is_numeric("79068"))
	testing.expect(t, !is_numeric("+123 ABC$"))
	{
		b := [1]u8{0x01}
		testing.expect(t, !is_numeric(string(b[:])))
	}
	{
		b := [1]u8{0x7F}
		testing.expect(t, !is_numeric(string(b[:])))
	}
	{
		b := [1]u8{0x80}
		testing.expect(t, !is_numeric(string(b[:])))
	}
	{
		b := [1]u8{0xC0}
		testing.expect(t, !is_numeric(string(b[:])))
	}
	{
		b := [1]u8{0xFF}
		testing.expect(t, !is_numeric(string(b[:])))
	}
}

@(test)
test_calc_segment_buffer_size :: proc(t: ^testing.T) {
	// NUMERIC: {num_chars, expected}
	{
		cases := [?][2]int {
			{0, 0},
			{1, 1},
			{2, 1},
			{3, 2},
			{4, 2},
			{5, 3},
			{6, 3},
			{1472, 614},
			{2097, 874},
			{5326, 2220},
			{9828, 4095},
			{9829, 4096},
			{9830, 4096},
			{9831, -1},
			{9832, -1},
			{12000, -1},
			{28453, -1},
			{55555, -1},
		}
		for tc in cases {
			testing.expect_value(t, calc_segment_buffer_size(.Numeric, tc[0]), tc[1])
		}
	}
	// ALPHANUMERIC
	{
		cases := [?][2]int {
			{0, 0},
			{1, 1},
			{2, 2},
			{3, 3},
			{4, 3},
			{5, 4},
			{6, 5},
			{1472, 1012},
			{2097, 1442},
			{5326, 3662},
			{5955, 4095},
			{5956, 4095},
			{5957, 4096},
			{5958, -1},
			{5959, -1},
			{12000, -1},
			{28453, -1},
			{55555, -1},
		}
		for tc in cases {
			testing.expect_value(t, calc_segment_buffer_size(.Alphanumeric, tc[0]), tc[1])
		}
	}
	// BYTE
	{
		cases := [?][2]int {
			{0, 0},
			{1, 1},
			{2, 2},
			{3, 3},
			{1472, 1472},
			{2097, 2097},
			{4094, 4094},
			{4095, 4095},
			{4096, -1},
			{4097, -1},
			{5957, -1},
			{12000, -1},
			{28453, -1},
			{55555, -1},
		}
		for tc in cases {
			testing.expect_value(t, calc_segment_buffer_size(.Byte, tc[0]), tc[1])
		}
	}
	// KANJI
	{
		cases := [?][2]int {
			{0, 0},
			{1, 2},
			{2, 4},
			{3, 5},
			{1472, 2392},
			{2097, 3408},
			{2519, 4094},
			{2520, 4095},
			{2521, -1},
			{5957, -1},
			{2522, -1},
			{12000, -1},
			{28453, -1},
			{55555, -1},
		}
		for tc in cases {
			testing.expect_value(t, calc_segment_buffer_size(.Kanji, tc[0]), tc[1])
		}
	}
	// ECI
	{
		testing.expect_value(t, calc_segment_buffer_size(.Eci, 0), 3)
	}
}

@(test)
test_calc_segment_bit_length :: proc(t: ^testing.T) {
	// NUMERIC: {num_chars, expected}
	{
		cases := [?][2]int {
			{0, 0},
			{1, 4},
			{2, 7},
			{3, 10},
			{4, 14},
			{5, 17},
			{6, 20},
			{1472, 4907},
			{2097, 6990},
			{5326, 17754},
			{9828, 32760},
			{9829, 32764},
			{9830, 32767},
			{9831, -1},
			{9832, -1},
			{12000, -1},
			{28453, -1},
		}
		for tc in cases {
			testing.expect_value(t, calc_segment_bit_length(.Numeric, tc[0]), tc[1])
		}
	}
	// ALPHANUMERIC
	{
		cases := [?][2]int {
			{0, 0},
			{1, 6},
			{2, 11},
			{3, 17},
			{4, 22},
			{5, 28},
			{6, 33},
			{1472, 8096},
			{2097, 11534},
			{5326, 29293},
			{5955, 32753},
			{5956, 32758},
			{5957, 32764},
			{5958, -1},
			{5959, -1},
			{12000, -1},
			{28453, -1},
		}
		for tc in cases {
			testing.expect_value(t, calc_segment_bit_length(.Alphanumeric, tc[0]), tc[1])
		}
	}
	// BYTE
	{
		cases := [?][2]int {
			{0, 0},
			{1, 8},
			{2, 16},
			{3, 24},
			{1472, 11776},
			{2097, 16776},
			{4094, 32752},
			{4095, 32760},
			{4096, -1},
			{4097, -1},
			{5957, -1},
			{12000, -1},
			{28453, -1},
		}
		for tc in cases {
			testing.expect_value(t, calc_segment_bit_length(.Byte, tc[0]), tc[1])
		}
	}
	// KANJI
	{
		cases := [?][2]int {
			{0, 0},
			{1, 13},
			{2, 26},
			{3, 39},
			{1472, 19136},
			{2097, 27261},
			{2519, 32747},
			{2520, 32760},
			{2521, -1},
			{5957, -1},
			{2522, -1},
			{12000, -1},
			{28453, -1},
		}
		for tc in cases {
			testing.expect_value(t, calc_segment_bit_length(.Kanji, tc[0]), tc[1])
		}
	}
	// ECI
	testing.expect_value(t, calc_segment_bit_length(.Eci, 0), 24)
}

@(test)
test_make_bytes :: proc(t: ^testing.T) {
	// Empty
	{
		seg := make_bytes(nil, nil)
		testing.expect_value(t, seg.mode, Mode.Byte)
		testing.expect_value(t, seg.num_chars, 0)
		testing.expect_value(t, seg.bit_length, 0)
		testing.expect(t, seg.data == nil)
	}
	// Single byte {0x00}
	{
		data := [1]u8{0x00}
		buf: [1]u8
		seg := make_bytes(data[:], buf[:])
		testing.expect_value(t, seg.mode, Mode.Byte)
		testing.expect_value(t, seg.num_chars, 1)
		testing.expect_value(t, seg.bit_length, 8)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0x00))
	}
	// Three bytes {0xEF, 0xBB, 0xBF}
	{
		data := [3]u8{0xEF, 0xBB, 0xBF}
		buf: [3]u8
		seg := make_bytes(data[:], buf[:])
		testing.expect_value(t, seg.mode, Mode.Byte)
		testing.expect_value(t, seg.num_chars, 3)
		testing.expect_value(t, seg.bit_length, 24)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0xEF))
		testing.expect_value(t, seg.data[1], u8(0xBB))
		testing.expect_value(t, seg.data[2], u8(0xBF))
	}
}

@(test)
test_make_numeric :: proc(t: ^testing.T) {
	// Empty
	{
		seg := make_numeric("", nil)
		testing.expect_value(t, seg.mode, Mode.Numeric)
		testing.expect_value(t, seg.num_chars, 0)
		testing.expect_value(t, seg.bit_length, 0)
	}
	// "9"
	{
		buf: [1]u8
		seg := make_numeric("9", buf[:])
		testing.expect_value(t, seg.mode, Mode.Numeric)
		testing.expect_value(t, seg.num_chars, 1)
		testing.expect_value(t, seg.bit_length, 4)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0x90))
	}
	// "81"
	{
		buf: [1]u8
		seg := make_numeric("81", buf[:])
		testing.expect_value(t, seg.mode, Mode.Numeric)
		testing.expect_value(t, seg.num_chars, 2)
		testing.expect_value(t, seg.bit_length, 7)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0xA2))
	}
	// "673"
	{
		buf: [2]u8
		seg := make_numeric("673", buf[:])
		testing.expect_value(t, seg.mode, Mode.Numeric)
		testing.expect_value(t, seg.num_chars, 3)
		testing.expect_value(t, seg.bit_length, 10)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0xA8))
		testing.expect_value(t, seg.data[1], u8(0x40))
	}
	// "3141592653"
	{
		buf: [5]u8
		seg := make_numeric("3141592653", buf[:])
		testing.expect_value(t, seg.mode, Mode.Numeric)
		testing.expect_value(t, seg.num_chars, 10)
		testing.expect_value(t, seg.bit_length, 34)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0x4E))
		testing.expect_value(t, seg.data[1], u8(0x89))
		testing.expect_value(t, seg.data[2], u8(0xF4))
		testing.expect_value(t, seg.data[3], u8(0x24))
		testing.expect_value(t, seg.data[4], u8(0xC0))
	}
}

@(test)
test_make_alphanumeric :: proc(t: ^testing.T) {
	// Empty
	{
		seg := make_alphanumeric("", nil)
		testing.expect_value(t, seg.mode, Mode.Alphanumeric)
		testing.expect_value(t, seg.num_chars, 0)
		testing.expect_value(t, seg.bit_length, 0)
	}
	// "A"
	{
		buf: [1]u8
		seg := make_alphanumeric("A", buf[:])
		testing.expect_value(t, seg.mode, Mode.Alphanumeric)
		testing.expect_value(t, seg.num_chars, 1)
		testing.expect_value(t, seg.bit_length, 6)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0x28))
	}
	// "%:"
	{
		buf: [2]u8
		seg := make_alphanumeric("%:", buf[:])
		testing.expect_value(t, seg.mode, Mode.Alphanumeric)
		testing.expect_value(t, seg.num_chars, 2)
		testing.expect_value(t, seg.bit_length, 11)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0xDB))
		testing.expect_value(t, seg.data[1], u8(0x40))
	}
	// "Q R"
	{
		buf: [3]u8
		seg := make_alphanumeric("Q R", buf[:])
		testing.expect_value(t, seg.mode, Mode.Alphanumeric)
		testing.expect_value(t, seg.num_chars, 3)
		testing.expect_value(t, seg.bit_length, 17)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0x96))
		testing.expect_value(t, seg.data[1], u8(0xCD))
		testing.expect_value(t, seg.data[2], u8(0x80))
	}
}

@(test)
test_make_eci :: proc(t: ^testing.T) {
	// 127
	{
		buf: [3]u8
		seg := make_eci(127, buf[:])
		testing.expect_value(t, seg.mode, Mode.Eci)
		testing.expect_value(t, seg.num_chars, 0)
		testing.expect_value(t, seg.bit_length, 8)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0x7F))
	}
	// 10345
	{
		buf: [3]u8
		seg := make_eci(10345, buf[:])
		testing.expect_value(t, seg.mode, Mode.Eci)
		testing.expect_value(t, seg.num_chars, 0)
		testing.expect_value(t, seg.bit_length, 16)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0xA8))
		testing.expect_value(t, seg.data[1], u8(0x69))
	}
	// 999999
	{
		buf: [3]u8
		seg := make_eci(999999, buf[:])
		testing.expect_value(t, seg.mode, Mode.Eci)
		testing.expect_value(t, seg.num_chars, 0)
		testing.expect_value(t, seg.bit_length, 24)
		testing.expect(t, seg.data != nil)
		testing.expect_value(t, seg.data[0], u8(0xCF))
		testing.expect_value(t, seg.data[1], u8(0x42))
		testing.expect_value(t, seg.data[2], u8(0x3F))
	}
}

@(test)
test_get_total_bits :: proc(t: ^testing.T) {
	// Empty segments
	{
		empty: []Segment
		testing.expect_value(t, get_total_bits(empty, 1), 0)
		testing.expect_value(t, get_total_bits(empty, 40), 0)
	}
	// Single BYTE segment: {.BYTE, 3, nil, 24}
	{
		segs := [?]Segment{{mode = .Byte, num_chars = 3, data = nil, bit_length = 24}}
		testing.expect_value(t, get_total_bits(segs[:], 2), 36)
		testing.expect_value(t, get_total_bits(segs[:], 10), 44)
		testing.expect_value(t, get_total_bits(segs[:], 39), 44)
	}
	// Mixed segments: ECI + NUMERIC + ALPHANUMERIC + KANJI
	{
		segs := [?]Segment {
			{mode = .Eci, num_chars = 0, data = nil, bit_length = 8},
			{mode = .Numeric, num_chars = 7, data = nil, bit_length = 24},
			{mode = .Alphanumeric, num_chars = 1, data = nil, bit_length = 6},
			{mode = .Kanji, num_chars = 4, data = nil, bit_length = 52},
		}
		testing.expect_value(t, get_total_bits(segs[:], 9), 133)
		testing.expect_value(t, get_total_bits(segs[:], 21), 139)
		testing.expect_value(t, get_total_bits(segs[:], 27), 145)
	}
	// Large BYTE segment: {.BYTE, 4093, nil, 32744}
	{
		segs := [?]Segment{{mode = .Byte, num_chars = 4093, data = nil, bit_length = 32744}}
		testing.expect_value(t, get_total_bits(segs[:], 1), -1)
		testing.expect_value(t, get_total_bits(segs[:], 10), 32764)
		testing.expect_value(t, get_total_bits(segs[:], 27), 32764)
	}
	// 5 segments: 4x {.NUMERIC,2047,nil,6824} + 1x {.NUMERIC,1617,nil,5390}
	{
		segs := [?]Segment {
			{mode = .Numeric, num_chars = 2047, data = nil, bit_length = 6824},
			{mode = .Numeric, num_chars = 2047, data = nil, bit_length = 6824},
			{mode = .Numeric, num_chars = 2047, data = nil, bit_length = 6824},
			{mode = .Numeric, num_chars = 2047, data = nil, bit_length = 6824},
			{mode = .Numeric, num_chars = 1617, data = nil, bit_length = 5390},
		}
		testing.expect_value(t, get_total_bits(segs[:], 1), -1)
		testing.expect_value(t, get_total_bits(segs[:], 10), 32766)
		testing.expect_value(t, get_total_bits(segs[:], 27), -1)
	}
	// 10 segments: 9x {.KANJI,255,nil,3315} + 1x {.ALPHANUMERIC,511,nil,2811}
	{
		segs := [?]Segment {
			{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
			{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
			{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
			{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
			{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
			{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
			{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
			{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
			{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
			{mode = .Alphanumeric, num_chars = 511, data = nil, bit_length = 2811},
		}
		testing.expect_value(t, get_total_bits(segs[:], 9), 32767)
		testing.expect_value(t, get_total_bits(segs[:], 26), -1)
		testing.expect_value(t, get_total_bits(segs[:], 40), -1)
	}
}

@(test)
test_min_buffer_size_text :: proc(t: ^testing.T) {
	// Empty string fits in version 1 at any ECL
	{
		size, ok := min_buffer_size("", .Low)
		testing.expect(t, ok)
		testing.expect_value(t, size, buffer_len_for_version(VERSION_MIN))
	}
	{
		size, ok := min_buffer_size("", .High)
		testing.expect(t, ok)
		testing.expect_value(t, size, buffer_len_for_version(VERSION_MIN))
	}

	// "Hello, world!" is short byte-mode text, should fit in a small version
	size, size_ok := min_buffer_size("Hello, world!", .Low)
	testing.expect(t, size_ok)
	testing.expect(t, size > 0)
	testing.expect(t, size <= buffer_len_for_version(3))

	// Pure numeric string gets numeric mode — more compact than byte
	num_size, num_ok := min_buffer_size("314159265358979323846264338327950288419716939937510", .Medium)
	byte_size, byte_ok := min_buffer_size_binary(51, .Medium) // same length forced to byte mode
	testing.expect(t, num_ok)
	testing.expect(t, byte_ok)
	testing.expect(t, num_size > 0)
	testing.expect(t, num_size <= byte_size) // numeric mode should be at least as compact

	// Alphanumeric string
	alnum_size, alnum_ok := min_buffer_size("HELLO WORLD", .Low)
	testing.expect(t, alnum_ok)
	testing.expect(t, alnum_size > 0)

	// Verify it agrees with what encode_text actually picks
	{
		text :: "Hello, world!"
		planned, planned_ok := min_buffer_size(text, .Low)
		testing.expect(t, planned_ok)
		testing.expect(t, planned > 0)
		qrcode: [BUFFER_LEN_MAX]u8
		temp: [BUFFER_LEN_MAX]u8
		ok := encode_text_manual(text, temp[:], qrcode[:], Ecc.Low)
		testing.expect(t, ok)
		actual_version_size := get_size(qrcode[:])
		actual_buf_len := buffer_len_for_version((actual_version_size - 17) / 4)
		testing.expect_value(t, planned, actual_buf_len)
	}

	// Constrained max_version: short text should fit in v1
	{
		hi_size, ok := min_buffer_size("Hi", .Low, max_version = 1)
		testing.expect(t, ok)
		testing.expect(t, hi_size > 0)
	}

	// Constrained max_version: long text should fail in v1
	long_text := "This string is definitely too long to fit in a version 1 QR code at high ECC"
	{
		_, ok := min_buffer_size(long_text, .High, max_version = 1)
		testing.expect(t, !ok)
	}

	// min_version raises the floor
	{
		hi_size, ok := min_buffer_size("Hi", .Low, min_version = 5)
		testing.expect(t, ok)
		testing.expect_value(t, hi_size, buffer_len_for_version(5))
	}

	// Higher ECL requires more capacity → may need a larger version
	low_size, low_ok := min_buffer_size("Hello, world!", .Low)
	high_size, high_ok := min_buffer_size("Hello, world!", .High)
	testing.expect(t, low_ok)
	testing.expect(t, high_ok)
	testing.expect(t, high_size >= low_size)
}

@(test)
test_min_buffer_size_binary :: proc(t: ^testing.T) {
	// Empty payload
	{
		size, ok := min_buffer_size(0, .Low)
		testing.expect(t, ok)
		testing.expect_value(t, size, buffer_len_for_version(VERSION_MIN))
	}

	// Small payload
	size, size_ok := min_buffer_size(10, .Low)
	testing.expect(t, size_ok)
	testing.expect(t, size > 0)
	testing.expect(t, size <= buffer_len_for_version(2))

	// Verify agreement with encode_binary_manual
	{
		data_len :: 100
		planned, planned_ok := min_buffer_size(data_len, .Medium)
		testing.expect(t, planned_ok)
		testing.expect(t, planned > 0)
		dat: [BUFFER_LEN_MAX]u8
		qrcode: [BUFFER_LEN_MAX]u8
		for i in 0 ..< data_len {
			dat[i] = u8(i)
		}
		ok := encode_binary_manual(dat[:], data_len, qrcode[:], .Medium)
		testing.expect(t, ok)
		actual_version_size := get_size(qrcode[:])
		actual_buf_len := buffer_len_for_version((actual_version_size - 17) / 4)
		testing.expect_value(t, planned, actual_buf_len)
	}

	// Max capacity at LOW: 2953 bytes should fit, something huge should not
	{
		size, ok := min_buffer_size(2953, .Low)
		testing.expect(t, ok)
		testing.expect(t, size > 0)
	}
	{
		_, ok := min_buffer_size(3000, .Low)
		testing.expect(t, !ok)
	}

	// Constrained version
	{
		_, ok := min_buffer_size(500, .Low, max_version = 5)
		testing.expect(t, !ok)
	}
}

@(test)
test_min_buffer_size_segments :: proc(t: ^testing.T) {
	// Empty segments
	{
		size, ok := min_buffer_size([]Segment{}, .Low)
		testing.expect(t, ok)
		testing.expect_value(t, size, buffer_len_for_version(VERSION_MIN))
	}

	// Single numeric segment
	{
		buf: [32]u8
		seg := make_numeric("12345", buf[:])
		segs := [1]Segment{seg}
		size, size_ok := min_buffer_size(segs[:], .Low)
		testing.expect(t, size_ok)
		testing.expect(t, size > 0)
		testing.expect(t, size <= buffer_len_for_version(1))
	}

	// Mixed segments (alphanumeric + numeric) — the silver demo
	{
		buf0: [BUFFER_LEN_MAX]u8
		buf1: [BUFFER_LEN_MAX]u8
		segs := [2]Segment {
			make_alphanumeric("THE SQUARE ROOT OF 2 IS 1.", buf0[:]),
			make_numeric("41421356237309504880168872420969807856967187537694807317667973799", buf1[:]),
		}
		planned, planned_ok := min_buffer_size(segs[:], .Low)
		testing.expect(t, planned_ok)
		testing.expect(t, planned > 0)

		// Verify against actual encode
		qrcode: [BUFFER_LEN_MAX]u8
		temp: [BUFFER_LEN_MAX]u8
		ok := encode_segments_manual(segs[:], Ecc.Low, temp[:], qrcode[:])
		testing.expect(t, ok)
		actual_version_size := get_size(qrcode[:])
		actual_buf_len := buffer_len_for_version((actual_version_size - 17) / 4)
		testing.expect_value(t, planned, actual_buf_len)
	}

	// Overflow case: segments that exceed version 40
	{
		segs := [1]Segment{{mode = .Byte, num_chars = 3000, data = nil, bit_length = 24000}}
		_, ok := min_buffer_size(segs[:], .Low)
		testing.expect(t, !ok)
	}
}

@(test)
test_encode_text_auto :: proc(t: ^testing.T) {
	// Basic: auto variant produces identical output to explicit_temp
	{
		text :: "Hello, world!"
		qr_explicit: [BUFFER_LEN_MAX]u8
		temp: [BUFFER_LEN_MAX]u8
		ok_explicit := encode_text_manual(text, temp[:], qr_explicit[:], .Low)
		testing.expect(t, ok_explicit)

		qr_auto: [BUFFER_LEN_MAX]u8
		ok_auto := encode_text_auto(text, qr_auto[:], .Low)
		testing.expect(t, ok_auto)

		size := get_size(qr_explicit[:])
		buf_len := buffer_len_for_version((size - 17) / 4)
		for i in 0 ..< buf_len {
			testing.expect_value(t, qr_auto[i], qr_explicit[i])
		}
	}

	// Numeric mode
	{
		text :: "314159265358979323846264338327950288419716939937510"
		qr_explicit: [BUFFER_LEN_MAX]u8
		temp: [BUFFER_LEN_MAX]u8
		ok_explicit := encode_text_manual(text, temp[:], qr_explicit[:], .Medium)
		testing.expect(t, ok_explicit)

		qr_auto: [BUFFER_LEN_MAX]u8
		ok_auto := encode_text_auto(text, qr_auto[:], .Medium)
		testing.expect(t, ok_auto)

		size := get_size(qr_explicit[:])
		buf_len := buffer_len_for_version((size - 17) / 4)
		for i in 0 ..< buf_len {
			testing.expect_value(t, qr_auto[i], qr_explicit[i])
		}
	}

	// Alphanumeric mode
	{
		text :: "HELLO WORLD"
		qr_explicit: [BUFFER_LEN_MAX]u8
		temp: [BUFFER_LEN_MAX]u8
		ok_explicit := encode_text_manual(text, temp[:], qr_explicit[:], .Quartile)
		testing.expect(t, ok_explicit)

		qr_auto: [BUFFER_LEN_MAX]u8
		ok_auto := encode_text_auto(text, qr_auto[:], .Quartile)
		testing.expect(t, ok_auto)

		size := get_size(qr_explicit[:])
		buf_len := buffer_len_for_version((size - 17) / 4)
		for i in 0 ..< buf_len {
			testing.expect_value(t, qr_auto[i], qr_explicit[i])
		}
	}

	// Empty string
	{
		qr_auto: [BUFFER_LEN_MAX]u8
		ok := encode_text_auto("", qr_auto[:], .Low)
		testing.expect(t, ok)
	}

	// With specific mask
	{
		text :: "https://www.nayuki.io/"
		qr_explicit: [BUFFER_LEN_MAX]u8
		temp: [BUFFER_LEN_MAX]u8
		ok_explicit := encode_text_manual(text, temp[:], qr_explicit[:], .High, mask = .M3)
		testing.expect(t, ok_explicit)

		qr_auto: [BUFFER_LEN_MAX]u8
		ok_auto := encode_text_auto(text, qr_auto[:], .High, mask = .M3)
		testing.expect(t, ok_auto)

		size := get_size(qr_explicit[:])
		buf_len := buffer_len_for_version((size - 17) / 4)
		for i in 0 ..< buf_len {
			testing.expect_value(t, qr_auto[i], qr_explicit[i])
		}
	}

	// Oversized content returns false
	{
		long_text := "This string is definitely too long to fit in a version 1 QR code at high ECC"
		qr_auto: [BUFFER_LEN_MAX]u8
		ok := encode_text_auto(long_text, qr_auto[:], .High, max_version = 1)
		testing.expect(t, !ok)
		testing.expect_value(t, qr_auto[0], u8(0))
	}
}

@(test)
test_encode_segments_auto :: proc(t: ^testing.T) {
	// Mixed segments (silver demo): auto matches explicit_temp
	{
		buf0: [BUFFER_LEN_MAX]u8
		buf1: [BUFFER_LEN_MAX]u8
		segs := [2]Segment {
			make_alphanumeric("THE SQUARE ROOT OF 2 IS 1.", buf0[:]),
			make_numeric("41421356237309504880168872420969807856967187537694807317667973799", buf1[:]),
		}

		qr_explicit: [BUFFER_LEN_MAX]u8
		temp: [BUFFER_LEN_MAX]u8
		ok_explicit := encode_segments_manual(segs[:], .Low, temp[:], qr_explicit[:])
		testing.expect(t, ok_explicit)

		qr_auto: [BUFFER_LEN_MAX]u8
		ok_auto := encode_segments_auto(segs[:], .Low, qr_auto[:])
		testing.expect(t, ok_auto)

		size := get_size(qr_explicit[:])
		buf_len := buffer_len_for_version((size - 17) / 4)
		for i in 0 ..< buf_len {
			testing.expect_value(t, qr_auto[i], qr_explicit[i])
		}
	}

	// Empty segments
	{
		qr_auto: [BUFFER_LEN_MAX]u8
		ok := encode_segments_auto(nil, .Low, qr_auto[:])
		testing.expect(t, ok)
	}
}

@(test)
test_encode_segments_advanced_auto :: proc(t: ^testing.T) {
	// Single numeric segment with constrained version range
	{
		buf: [BUFFER_LEN_MAX]u8
		seg := make_numeric("12345678901234567890", buf[:])
		segs := [1]Segment{seg}

		qr_explicit: [BUFFER_LEN_MAX]u8
		temp: [BUFFER_LEN_MAX]u8
		ok_explicit := encode_segments_advanced_manual(
			segs[:],
			.Medium,
			VERSION_MIN,
			10,
			nil,
			true,
			temp[:],
			qr_explicit[:],
		)
		testing.expect(t, ok_explicit)

		qr_auto: [BUFFER_LEN_MAX]u8
		ok_auto := encode_segments_advanced_auto(segs[:], .Medium, VERSION_MIN, 10, nil, true, qr_auto[:])
		testing.expect(t, ok_auto)

		size := get_size(qr_explicit[:])
		buf_len := buffer_len_for_version((size - 17) / 4)
		for i in 0 ..< buf_len {
			testing.expect_value(t, qr_auto[i], qr_explicit[i])
		}
	}

	// With specific mask and boost_ecl = false
	{
		buf: [BUFFER_LEN_MAX]u8
		seg := make_alphanumeric("TESTING 123", buf[:])
		segs := [1]Segment{seg}

		qr_explicit: [BUFFER_LEN_MAX]u8
		temp: [BUFFER_LEN_MAX]u8
		ok_explicit := encode_segments_advanced_manual(
			segs[:],
			.High,
			1,
			40,
			Mask.M5,
			false,
			temp[:],
			qr_explicit[:],
		)
		testing.expect(t, ok_explicit)

		qr_auto: [BUFFER_LEN_MAX]u8
		ok_auto := encode_segments_advanced_auto(segs[:], .High, 1, 40, Mask.M5, false, qr_auto[:])
		testing.expect(t, ok_auto)

		size := get_size(qr_explicit[:])
		buf_len := buffer_len_for_version((size - 17) / 4)
		for i in 0 ..< buf_len {
			testing.expect_value(t, qr_auto[i], qr_explicit[i])
		}
	}

	// Oversized: too much data for max_version = 1
	{
		buf: [BUFFER_LEN_MAX]u8
		seg := make_alphanumeric("THIS IS A SOMEWHAT LONG STRING THAT WILL NOT FIT", buf[:])
		segs := [1]Segment{seg}

		qr_auto: [BUFFER_LEN_MAX]u8
		ok := encode_segments_advanced_auto(segs[:], .High, 1, 1, nil, true, qr_auto[:])
		testing.expect(t, !ok)
		testing.expect_value(t, qr_auto[0], u8(0))
	}
}
