// Deflate compression/decompression implementation
// This is a simplified implementation focusing on basic functionality

// Deflate compressed data structure

///|
pub struct DeflateData {
  compressed_bytes : Bytes
  original_size : Int
  adler32_checksum : @adler32.Adler32
}

// Compress data using deflate algorithm

///|
pub fn deflate_compress(data : Bytes, level : Level) -> DeflateData {
  // Work directly with bytes to avoid corruption
  match level {
    Level::None => compress_uncompressed(data)
    Level::Fast => compress_basic_deflate_bytes(data)
    Level::Default => compress_basic_deflate_bytes(data)
    Level::Best => compress_basic_deflate_bytes(data)
  }
}

// Decompress deflate data

///|
pub fn deflate_decompress(deflate_data : DeflateData) -> Bytes raise {
  // Decompress using bytes-based function to avoid String conversion corruption
  let decompressed_bytes = decompress_deflate_blocks_bytes(
    deflate_data.compressed_bytes,
  )
  // Verify Adler-32 checksum of the decompressed data
  let computed_adler = @adler32.bytes(decompressed_bytes)
  if computed_adler == deflate_data.adler32_checksum {
    decompressed_bytes
  } else {
    fail("Adler-32 checksum mismatch")
  }
}

// Compress using uncompressed blocks (Level::None)

///|
fn compress_uncompressed(data : Bytes) -> DeflateData {
  if data.length() == 0 {
    // Empty data - just return empty compressed data
    let adler = @adler32.bytes(data)
    return {
      compressed_bytes: Bytes::from_array([]),
      original_size: 0,
      adler32_checksum: adler,
    }
  }

  // For uncompressed blocks, we need to create deflate blocks
  // Each block has a 3-bit header: BFINAL (1 bit) + BTYPE (2 bits)
  // For uncompressed: BTYPE = 00
  // Then LEN (16 bits) and NLEN (16 bits, one's complement of LEN)

  let compressed = @buffer.new()
  let mut remaining = data[:]

  // Process data in chunks (max 65535 bytes per uncompressed block)
  while remaining.length() > 0 {
    let chunk_size = if remaining.length() > 65535 {
      65535
    } else {
      remaining.length()
    }
    let chunk = remaining[0:chunk_size]
    let is_final = remaining.length() <= 65535

    // Create block header
    let bfinal = if is_final { 1 } else { 0 }
    let btype = @huffman.block_type_to_btype(@huffman.uncompressed_block_type())
    let header_bits = bfinal | (btype << 1) // 3 bits total

    // For simplicity, we'll store the header as a full byte (padding with zeros)
    let header_byte = header_bits
    // let header_bytes = Bytes::from_array([header_byte.to_byte()])
    // compressed = compressed + header_bytes
    compressed.write_byte(header_byte.to_byte())

    // Add LEN (little-endian 16-bit)
    let len = chunk.length()
    let len_bytes = write_u16_le_bytes(len)
    // compressed = compressed + len_bytes
    compressed.write_bytes(len_bytes)
    // Add NLEN (one's complement of LEN)
    let nlen = len ^ 0xffff
    let nlen_bytes = write_u16_le_bytes(nlen)
    // compressed = compressed + nlen_bytes
    compressed.write_bytes(nlen_bytes)

    // Add the actual data
    // compressed = compressed + chunk
    compressed.write_bytesview(chunk)

    // Move to next chunk
    remaining = remaining[chunk_size:remaining.length()]
  }
  let adler = @adler32.bytes(data)
  {
    compressed_bytes: compressed.to_bytes(),
    original_size: data.length(),
    adler32_checksum: adler,
  }
}

// Bytes-based basic deflate compression (no String conversion)

///|
fn compress_basic_deflate_bytes(data : Bytes) -> DeflateData {
  // Try Fixed Huffman compression for small data, otherwise use uncompressed
  if data.length() < 1000 {
    compress_fixed_huffman_bytes(data)
  } else {
    // For larger data, fall back to uncompressed blocks for now
    compress_uncompressed(data)
  }
}

// Bytes-based Fixed Huffman compression (no String conversion)

///|
fn compress_fixed_huffman_bytes(data : Bytes) -> DeflateData {
  if data.length() == 0 {
    // Empty data - just return empty compressed data
    let adler = @adler32.bytes(data)
    return {
      compressed_bytes: Bytes::from_array([]),
      original_size: 0,
      adler32_checksum: adler,
    }
  }

  // Create Fixed Huffman block
  let mut compressed = Bytes::from_array([])

  // Block header: BFINAL=1 (final block), BTYPE=01 (Fixed Huffman)
  let bfinal = 1
  let btype = @huffman.block_type_to_btype(@huffman.fixed_huffman_block_type())
  let header_bits = bfinal | (btype << 1)
  let header_byte = header_bits
  let header_bytes = Bytes::from_array([header_byte.to_byte()])
  compressed = compressed + header_bytes

  // For now, encode each byte as a literal using Fixed Huffman codes
  // This is a simplified implementation - a full implementation would:
  // 1. Use LZ77 to find matches
  // 2. Encode literals and length/distance pairs
  // 3. Use proper Fixed Huffman encoding

  // Simplified: just store the data with minimal encoding
  compressed = compressed + data

  // Add end-of-block symbol (would be properly Huffman encoded in real implementation)
  let end_block_byte : Int = 0
  let end_block = Bytes::from_array([end_block_byte.to_byte()]) // Placeholder for end-of-block
  compressed = compressed + end_block
  let adler = @adler32.bytes(data)
  {
    compressed_bytes: compressed,
    original_size: data.length(),
    adler32_checksum: adler,
  }
}

// Decompress deflate blocks from bytes (handles binary data correctly)

///|
fn decompress_deflate_blocks_bytes(compressed : Bytes) -> Bytes raise {
  if compressed.length() == 0 {
    return Bytes::from_array([])
  }
  let mut result = Bytes::from_array([])
  let mut offset = 0
  while offset < compressed.length() {
    // Read block header (3 bits, but we stored as full byte)
    if offset >= compressed.length() {
      fail("Unexpected end of compressed data")
    }
    let header_byte = compressed[offset].to_int()
    offset = offset + 1
    let bfinal = header_byte & 1
    let btype_value = (header_byte >> 1) & 3
    match @huffman.btype_to_block_type(btype_value) {
      Some(block_type) =>
        if block_type == @huffman.uncompressed_block_type() {
          // Uncompressed block
          let (block_data, new_offset) = decompress_uncompressed_block_bytes(
            compressed, offset,
          )
          result = result + block_data
          offset = new_offset
        } else if block_type == @huffman.fixed_huffman_block_type() {
          // Fixed Huffman block - use bytes-based decompression
          let (block_data, new_offset) = @huffman.decompress_fixed_huffman_block_bytes(
            compressed, offset,
          )
          result = result + block_data
          offset = new_offset
        } else if block_type == @huffman.dynamic_huffman_block_type() {
          // Dynamic Huffman - not implemented yet
          fail("Dynamic Huffman decompression not implemented")
        } else {
          fail("Unknown block type")
        }
      None => fail("Invalid block type: " + btype_value.to_string())
    }

    // If this was the final block, we're done
    if bfinal == 1 {
      break
    }
  }
  result
}

// Decompress an uncompressed block from bytes (handles binary data correctly)

///|
fn decompress_uncompressed_block_bytes(
  compressed : Bytes,
  offset : Int,
) -> (Bytes, Int) raise {
  if offset + 4 > compressed.length() {
    fail("Incomplete uncompressed block header")
  }

  // Read LEN (16-bit little-endian) from bytes
  let len = read_u16_le_bytes(compressed, offset)
  let nlen = read_u16_le_bytes(compressed, offset + 2)

  // Verify NLEN is one's complement of LEN
  if (len ^ nlen) != 0xffff {
    fail("Invalid uncompressed block: LEN/NLEN mismatch")
  }
  let data_offset = offset + 4
  if data_offset + len > compressed.length() {
    fail("Incomplete uncompressed block data")
  }

  // Extract the block data as bytes
  let block_data = compressed[data_offset:data_offset + len].to_bytes()
  (block_data, data_offset + len)
}

// Helper functions for binary data (reused from zip module)

///|
fn write_u16_le_bytes(value : Int) -> Bytes {
  let b0 = value & 0xff
  let b1 = (value >> 8) & 0xff
  Bytes::from_array([b0.to_byte(), b1.to_byte()])
}

///|
fn read_u16_le_bytes(data : Bytes, offset : Int) -> Int {
  if offset + 1 >= data.length() {
    return 0
  }
  let b0 = data[offset].to_int()
  let b1 = data[offset + 1].to_int()
  b0 + (b1 << 8)
}

// Create deflate-compressed file data for ZIP (DEPRECATED - use deflate_of_bytes)

///|
pub fn deflate_of_bytes(data : Bytes, level : Level) -> Bytes {
  match deflate_compress(data, level) {
    deflate_data => deflate_data.compressed_bytes
  }
}

// Bytes-based API functions (more efficient for binary data)

///|
pub fn deflate_decompress_bytes(
  compressed_data : Bytes,
  _original_size : Int,
) -> Bytes raise {
  // Use bytes-based decompression to avoid String conversion corruption
  decompress_deflate_blocks_bytes(compressed_data)
}

// Raw deflate decompression from bytes (modern API)

///|
pub fn deflate_decompress_raw_bytes(
  compressed_data : Bytes,
  _original_size : Int,
) -> Bytes raise {
  // Use bytes-based decompression to avoid String conversion corruption
  decompress_deflate_blocks_bytes(compressed_data)
}