///|
/// A bit array segment: `value` then `:` options separated by `-`. The same
/// shape serves patterns, constants and expressions, so the value parser,
/// the argument parser (for `size(...)`), the `Int` builder and the
/// location reader are parameters.
fn[A] Parser::parse_bit_array_segment(
  self : Parser,
  value_parser : (Parser) -> A? raise ParseError,
  arg_parser : (Parser) -> A raise ParseError,
  to_int_segment : (String, BigInt, Int32, Int32) -> A,
  location_of : (A) -> @span.SrcSpan,
) -> @ast.BitArraySegment[A]? raise ParseError {
  match value_parser(self) {
    Some(value) => {
      let options : Array[@ast.BitArrayOption[A]] = if self.maybe_one(Colon)
        is Some(_) {
        self.series_of(
          p => p.parse_bit_array_option(arg_parser, to_int_segment),
          Some(Minus),
        )
      } else {
        []
      }
      let end = match options.last() {
        Some(option) => bit_array_option_location(option).end
        None => location_of(value).end
      }
      Some({
        location: sp(location_of(value).start, end),
        value,
        options: options[:],
      })
    }
    None => None
  }
}

///|
fn[A] bit_array_option_location(
  option : @ast.BitArrayOption[A],
) -> @span.SrcSpan {
  match option {
    Bytes(location~)
    | Int(location~)
    | Float(location~)
    | Bits(location~)
    | Utf8(location~)
    | Utf16(location~)
    | Utf32(location~)
    | Utf8Codepoint(location~)
    | Utf16Codepoint(location~)
    | Utf32Codepoint(location~)
    | Signed(location~)
    | Unsigned(location~)
    | Big(location~)
    | Little(location~)
    | Native(location~)
    | Size(location~, ..)
    | Unit(location~, ..) => location
  }
}

///|
/// One option: `utf8`, `size(x)`, `unit(2)` or a bare int (`size` in short
/// form).
fn[A] Parser::parse_bit_array_option(
  self : Parser,
  arg_parser : (Parser) -> A raise ParseError,
  to_int_segment : (String, BigInt, Int32, Int32) -> A,
) -> @ast.BitArrayOption[A]? raise ParseError {
  match self.token0 {
    Some({ kind: Name(name), span, .. }) => {
      self.advance()
      if self.maybe_one(LeftParen) is Some(_) {
        match name {
          "unit" =>
            match self.token0 {
              Some({ kind: Int(value~, ..), span: int_span, .. }) => {
                self.advance()
                let (_, end) = self.expect_one(RightParen)
                match parse_unit(value) {
                  Some(units) =>
                    Some(Unit(location=sp(span.start, end), value=units))
                  None => parse_error(InvalidBitArrayUnit, int_span)
                }
              }
              _ => self.next_token_unexpected(["A positive int"])
            }
          "size" => {
            let value = arg_parser(self)
            let (_, end) = self.expect_one(RightParen)
            Some(Size(location=sp(span.start, end), value~, short_form=false))
          }
          _ => parse_error(InvalidBitArraySegment, span)
        }
      } else {
        match str_to_bit_array_option(name, span) {
          Some(option) => Some(option)
          None => parse_error(InvalidBitArraySegment, span)
        }
      }
    }
    Some({ kind: Int(value~, int_value~), span, .. }) => {
      self.advance()
      Some(
        Size(
          location=span,
          value=to_int_segment(value, int_value, span.start, span.end),
          short_form=true,
        ),
      )
    }
    _ =>
      self.next_token_unexpected([
        "A valid bit array segment type", "See: https://tour.gleam.run/data-types/bit-arrays/",
      ])
  }
}

///|
/// The compiler's `u8::from_str(value.replace("_", ""))` filtered to `> 0`:
/// decimal digits only, 1 to 255.
fn parse_unit(value : String) -> Int32? {
  let digits = value.replace(old="_", new="")
  match parse_u64(digits) {
    Some(n) if n >= 1 && n <= 255 => Some(n.to_int())
    _ => None
  }
}

///|
fn[A] str_to_bit_array_option(
  name : String,
  location : @span.SrcSpan,
) -> @ast.BitArrayOption[A]? {
  match name {
    "bytes" => Some(Bytes(location~))
    "int" => Some(Int(location~))
    "float" => Some(Float(location~))
    "bits" => Some(Bits(location~))
    "utf8" => Some(Utf8(location~))
    "utf16" => Some(Utf16(location~))
    "utf32" => Some(Utf32(location~))
    "utf8_codepoint" => Some(Utf8Codepoint(location~))
    "utf16_codepoint" => Some(Utf16Codepoint(location~))
    "utf32_codepoint" => Some(Utf32Codepoint(location~))
    "signed" => Some(Signed(location~))
    "unsigned" => Some(Unsigned(location~))
    "big" => Some(Big(location~))
    "little" => Some(Little(location~))
    "native" => Some(Native(location~))
    _ => None
  }
}

///|
fn Parser::expect_bit_array_pattern_segment_argument(
  self : Parser,
) -> @ast.Pattern raise ParseError {
  BitArraySize(self.expect_bit_array_size())
}

///|
/// A size expression in a bit array pattern: ints, names, `{ }` blocks and
/// the integer operators, by precedence.
fn Parser::expect_bit_array_size(
  self : Parser,
) -> @ast.BitArraySize raise ParseError {
  let opstack : Array[OpEntry] = []
  let estack : Array[@ast.BitArraySize] = [self.parse_bit_array_size_unit()]
  while true {
    guard self.token0 is Some(t) else { break }
    guard token_to_int_operator(t.kind) is Some(op) else { break }
    self.advance()
    ignore(
      handle_operator(
        Some({ token: t, precedence: int_operator_precedence(op), }),
        opstack,
        estack,
        reduce_bit_array_size,
      ),
    )
    estack.push(self.parse_bit_array_size_unit())
  }
  match handle_operator(None, opstack, estack, reduce_bit_array_size) {
    Some(size) => size
    None => self.next_token_unexpected(["A variable name or an int"])
  }
}

///|
fn Parser::parse_bit_array_size_unit(
  self : Parser,
) -> @ast.BitArraySize raise ParseError {
  self.nested(op_bit_array_size, () => {
    match self.token0 {
      Some({ kind: Name(name), span, .. }) => {
        self.advance()
        Variable(location=span, name~)
      }
      Some({ kind: Int(value~, int_value~), span, .. }) => {
        self.advance()
        Int(location=span, value~, int_value~)
      }
      Some({ kind: LeftBrace, span, .. }) => {
        self.advance()
        let inner = self.expect_bit_array_size()
        let (_, end) = self.expect_one(RightBrace)
        Block(location=sp(span.start, end), inner~)
      }
      _ => self.next_token_unexpected(["A variable name or an int"])
    }
  })
}

///|
fn bit_array_size_int(
  value : String,
  int_value : BigInt,
  start : Int32,
  end : Int32,
) -> @ast.Pattern {
  BitArraySize(Int(location=sp(start, end), value~, int_value~))
}

///|
fn bit_array_expression_int(
  value : String,
  int_value : BigInt,
  start : Int32,
  end : Int32,
) -> @ast.Expr {
  Int(location=sp(start, end), value~, int_value~)
}

///|
fn bit_array_const_int(
  value : String,
  int_value : BigInt,
  start : Int32,
  end : Int32,
) -> @ast.Constant {
  Int(location=sp(start, end), value~, int_value~)
}