///|
/// Hex characters for IPv6 serialization
let hex_chars : FixedArray[Char] = [
  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
]

///|
/// Represents an IPv6 address as 8 16-bit pieces
struct IPv6(FixedArray[UInt16]) derive(Debug)

///|
/// Serialize IPv6 address per WHATWG URL spec
/// Finds longest run of consecutive zeros for :: compression
pub fn IPv6::to_string(self : IPv6) -> String {
  let address = self.0
  // Find the longest sequence of consecutive 0 pieces for compression
  let mut compress_start : Int? = None
  let mut compress_len = 0
  let mut current_start : Int? = None
  let mut current_len = 0
  for i = 0; i < 8; i = i + 1 {
    if address[i] == 0 {
      if current_start is None {
        current_start = Some(i)
        current_len = 1
      } else {
        current_len = current_len + 1
      }
    } else {
      if current_len > compress_len && current_len > 1 {
        compress_start = current_start
        compress_len = current_len
      }
      current_start = None
      current_len = 0
    }
  }
  // Check at the end
  if current_len > compress_len && current_len > 1 {
    compress_start = current_start
    compress_len = current_len
  }
  // Build the string
  let result = StringBuilder::new()
  let mut i = 0
  let mut ignore = false // Track if we just wrote :: and should not add another :
  while i < 8 {
    if compress_start is Some(start) && i == start {
      // Always write "::" for zero compression
      result.write_string("::")
      i = i + compress_len
      ignore = true
      continue
    }
    if i > 0 && !ignore {
      result.write_char(':')
    }
    ignore = false
    // Write hex value (lowercase, no leading zeros)
    let value = address[i].to_int()
    if value == 0 {
      result.write_char('0')
    } else {
      let mut started = false
      for shift = 12; shift >= 0; shift = shift - 4 {
        let digit = (value >> shift) & 0xf
        if digit != 0 || started {
          result.write_char(hex_chars[digit])
          started = true
        }
      }
    }
    i = i + 1
  }
  result.to_string()
}

///|
/// Implement Show trait for IPv6, outputting compressed colon notation
pub impl Show for IPv6 with fn output(self : IPv6, logger : &Logger) -> Unit {
  logger.write_string(self.to_string())
}

///|
/// Implement ToJson trait for IPv6, outputting as array of 8 16-bit values
pub impl ToJson for IPv6 with fn to_json(self : IPv6) -> Json {
  self.0.to_json()
}

///|
/// Parse an IPv6 address per WHATWG URL spec.
/// Supports :: compression and embedded IPv4 addresses.
pub fn IPv6::parse(input : StringView) -> IPv6 raise ValidationError {
  // 1. Let `address` be a new IPv6 address whose pieces are all 0.
  let address : FixedArray[UInt16] = FixedArray::make(8, 0)
  // 2. Let `piece_index` be `0`.
  let mut piece_index : Int = 0
  // 3. Let compress be `None`.
  let mut compress : Int? = None

  // 4. Let `pointer` be a pointer for `input`.
  //
  // Note: We do not have pointer arithmetic in Moonbit, so we will just
  //       manipulate the `input` directly in `match`/`loop` expressions below.

  // 5. If `c` is U+003A (:), then:
  let input = match input {
    [':', ':', .. remaining] => {
      // 5.3. Increase `piece_index` by 1 and then set compress to `piece_index`.
      piece_index = piece_index + 1
      compress = Some(piece_index)
      // 5.2. Increase `pointer` by 2.
      remaining
    }
    [':', ..] =>
      // 5.1. If `remaining` does not start with U+003A (:),
      //      IPv6-invalid-compression validation error, return failure.
      raise IPv6InvalidCompression
    input => input
  }

  // 6. While `c` is ...
  for input = input {
    match input {
      // ... not the EOF code point:
      [] => break
      // 6.1. If `piece_index` is 8, IPv6-too-many-pieces validation error,
      //      return failure.
      _ if piece_index == 8 => raise IPv6TooManyPieces
      // 6.2. If c is U+003A (:), then:
      [':', .. remaining] => {
        // 6.2.1. If `compress` is non-null, IPv6-multiple-compression validation
        //        error, return failure.
        if compress is Some(_) {
          raise IPv6MultipleCompression
        }
        // 6.2.2. increase pointer and `piece_index` by 1, set `compress` to
        //        `piece_index`, and then continue.
        piece_index = piece_index + 1
        compress = Some(piece_index)
        continue remaining
      }
      input => {
        // 6.3. Let `value` and `length` be 0.
        let mut value : UInt16 = 0
        let mut length = 0
        // 6.4. While `length` is less than 4 and c is an ASCII hex digit, set
        //      `value` to `value` × 0x10 + c interpreted as hexadecimal number,
        //      and increase pointer and `length` by 1.
        let remaining = for input = input; length < 4; {
          match input {
            ['0'..='9' as c, .. rest] => {
              value = value * 0x10 + (c.to_int().to_uint16() - '0')
              length = length + 1
              continue rest
            }
            ['a'..='f' as c, .. rest] => {
              value = value * 0x10 + (c.to_int().to_uint16() - 'a' + 10)
              length = length + 1
              continue rest
            }
            ['A'..='F' as c, .. rest] => {
              value = value * 0x10 + (c.to_int().to_uint16() - 'A' + 10)
              length = length + 1
              continue rest
            }
            _ => break input
          }
        } nobreak {
          input
        }
        let remaining = match remaining {
          [] => remaining
          // 6.5. If c is U+002E (.), then:
          ['.', ..] => {
            // 6.5.1. If [length] is 0, IPv4-in-IPv6-invalid-code-point validation
            //        error, return failure.
            if length == 0 {
              raise IPv4InIPv6InvalidCodePoint
            }
            // 6.5.2. Decrease pointer by length.
            //
            // Note: We does not have pointer arithmetic, so this is achieved by
            //       using `input` instead of `remaining` directly in the loop
            //       below.

            // 6.5.3. If `piece_index` is greater than 6,
            //        IPv4-in-IPv6-too-many-pieces validation error, return
            //        failure.
            if piece_index > 6 {
              raise IPv4InIPv6TooManyPieces
            }

            // 6.5.4. Let `numbers_seen` be 0.
            let mut numbers_seen = 0
            // 6.5.5. While `c` is not ...
            for input = input {
              match input {
                // ... the EOF code point:
                [] => break
                // 6.5.5.1. Let `ipv4_piece` be `None`.
                //
                // Note: Here we use `UInt16` directly since we will always
                //       initialize it in the loop below, and there is no need to
                //       have it initialized here as an `UInt16?`.
                //
                // 6.5.5.2. If `numbers_seen` is greater than 0, then:
                //    1. If c is U+002E (.), increase pointer and `numbers_seen`
                //       is less than 4, then increase `pointer` by 1.
                //    2. Otherwise, IPv4-in-IPv6-invalid-code-point validation
                //       error, return failure.
                //
                // Note: Here we test if the `c` is `.` first even if `numbers_seen`
                //       is `0`. This is because in the immediately following
                //       branching we will be testing if `c` is a digit, and raise
                //       the same error (IPv4-in-IPv6-invalid-code-point) if not.
                //       Therefore, the code logic here is equivalent to what is
                //       described in the spec.
                ['.', .. remaining] =>
                  if numbers_seen is (1..<4) {
                    continue remaining
                  } else {
                    raise IPv4InIPv6InvalidCodePoint
                  }
                // 6.5.5.4. While `c` is an ASCII digit:
                ['0'..='9' as c, .. remaining] => {
                  let mut ipv4_piece : UInt16 = c.to_int().to_uint16() - '0'
                  // 6.5.5.4. While `c` is an ASCII digit:
                  let remaining = for remaining = remaining {
                    match remaining {
                      ['0'..='9' as c, .. remaining] => {
                        // 6.5.5.4.1. Let `number` be `c` interpreted as decimal number.
                        let number = c.to_int().to_uint16() - '0'
                        // 6.5.5.4.2. If `ipv4_piece` is `None`, then set `ipv4_piece`
                        // │  to `number`.
                        // │
                        // │  Note: This is actually done above before entering
                        // │  this loop.
                        // │  Otherwise, if `ipv4_piece` is 0, IPv4-in-IPv6-invalid-code-point
                        // │  validation error, return failure.
                        if ipv4_piece is 0 {
                          raise IPv4InIPv6InvalidCodePoint
                        }
                        // │  Otherwise, set `ipv4_piece` to `ipv4_piece` × 10 + `number`.
                        ipv4_piece = ipv4_piece * 10 + number
                        // 6.5.5.4.3. If `ipv4_piece` is greater than 255, IPv4-in-IPv6-out-of-range-part
                        //    validation error, return failure.
                        if ipv4_piece > 255 {
                          raise IPv4InIPv6OutOfRangePart
                        }
                        // 6.5.5.4.4. Increase `pointer` by 1.
                        continue remaining
                      }
                      remaining => break remaining
                    }
                  }
                  // 6.5.5.5. Set `address[piece_index]` to
                  //          `address[piece_index]` × 0x100 + `ipv4_piece`.
                  address[piece_index] = address[piece_index] * 0x100 +
                    ipv4_piece
                  // 6.5.5.6. Increase `numbers_seen` by 1.
                  numbers_seen = numbers_seen + 1
                  // 6.5.5.7. If `numbers_seen` is 2 or 4, increase `piece_index`
                  //          by 1.
                  if numbers_seen is (2 | 4) {
                    piece_index = piece_index + 1
                  }
                  continue remaining
                }
                // 6.5.5.3. If `c` is not an ASCII digit, IPv4-in-IPv6-invalid-code-point
                //          validation error, return failure.
                [_, ..] => raise IPv4InIPv6InvalidCodePoint
              }
            }
            // 6.5.6. If `numbers_seen` is not 4, IPv4-in-IPv6-too-few-parts validation
            //        error, return failure.
            if numbers_seen != 4 {
              raise IPv4InIPv6TooFewParts
            }
            // 6.5.7. Break.
            break
          }
          // 6.6. Otherwise, if `c` is U+003A (:):
          // 6.6.2. If `c` is the EOF code point, IPv6-invalid-code-point validation error,
          //       return failure.
          [':'] => raise IPv6InvalidCodePoint
          // 6.6.1. Increase `pointer` by 1.
          [':', .. remaining] => remaining
          // 6.7. Otherwise, if `c` is not the EOF code point, IPv6-invalid-code-point
          //       validation error, return failure.
          [_, ..] => raise IPv6InvalidCodePoint
        }
        // 6.8. Set `address[piece_index]` to `value`.
        address[piece_index] = value
        // 6.9. Increase `piece_index` by 1.
        piece_index = piece_index + 1
        continue remaining
      }
    }
  }
  if compress is Some(compress) {
    // 7. If `compress` is non-null, then:

    // 7.1. Let `swaps` be `piece_index` - `compress`.
    let mut swaps = piece_index - compress
    // 7.2. Let `piece_index` be `7`.
    piece_index = 7
    // 7.3. While `swaps` is greater than `0`, swap `address[piece_index]` and
    //      `address[compress + swaps - 1]`, decrease `piece_index` by `1`, and
    //      decrease `swaps` by `1`.
    while piece_index != 0 && swaps > 0 {
      address.swap(piece_index, compress + swaps - 1)
      piece_index = piece_index - 1
      swaps = swaps - 1
    }
  } else if piece_index != 8 {
    // 8. Otherwise, if `compress` is null and `piece_index` is not `8`,
    //    IPv6-too-few-pieces validation error, return failure.
    raise IPv6TooFewPieces
  }
  // 9. Return address.
  IPv6(address)
}