///|
/// PKL-119b: method names dispatched off an `IntSeqValue` target.
/// `step` is overloaded — it's also a property name returning the
/// carrier slot — so the CallExpr dispatcher checks the call form
/// (zero args ⇒ method call returning a new IntSeq).
fn is_intseq_method_name(name : String) -> Bool {
  match name {
    "step" | "toList" | "toListing" | "map" | "fold" => true
    _ => false
  }
}

///|
/// PKL-119c: method names dispatched off a `SetValue` target. The
/// method-side surface mirrors Apple Pkl's `Set` class — the binary
/// `union` / `intersection` / `difference` operators come in a
/// follow-up; this slice covers `.contains` plus the read-only
/// projections and the higher-order helpers shared with `Listing`.
fn is_set_method_name(name : String) -> Bool {
  match name {
    "contains"
    | "startsWith"
    | "endsWith"
    // PKL-148: pkl:base Set surface — `add` / `every` / `any` /
    // `firstOrNull` / `getOrNull` / `count`.
    | "add"
    | "every"
    | "any"
    | "none"
    | "firstOrNull"
    | "lastOrNull"
    | "restOrNull"
    | "singleOrNull"
    | "count"
    | "toList"
    | "toListing"
    | "toSet"
    | "map"
    | "filter"
    | "fold"
    | "join"
    | "sortWith"
    | "flatMap"
    | "flatten"
    | "rest"
    | "last"
    | "first"
    | "single"
    | "find"
    | "findOrNull"
    | "findLast"
    | "findLastOrNull"
    | "take"
    | "takeWhile"
    | "takeLast"
    | "takeLastWhile"
    | "drop"
    | "dropWhile"
    | "dropLast"
    | "dropLastWhile"
    | "foldBack"
    | "reduce"
    | "reduceOrNull"
    | "groupBy"
    | "intersect"
    | "difference"
    | "toMap"
    | "minWith"
    | "minWithOrNull"
    | "maxWith"
    | "maxWithOrNull"
    | "zip"
    | "filterIndexed"
    | "mapIndexed"
    | "mapNonNullIndexed"
    | "flatMapIndexed"
    | "foldIndexed"
    | "toDynamic"
    | "filterNonNull"
    | "mapNonNull"
    | "filterIsInstance"
    | "split"
    | "splitOrNull"
    | "partition"
    | "min"
    | "minOrNull"
    | "max"
    | "maxOrNull"
    | "minBy"
    | "minByOrNull"
    | "maxBy"
    | "maxByOrNull"
    | "sort"
    | "sortBy"
    | "repeat"
    | "reverse" => true
    _ => false
  }
}

///|
/// PKL-119d: method names dispatched off a `MapValue` target. Apple
/// Pkl's `Map` class is read-mostly; this slice covers the
/// lookup methods (`getOrNull` / `containsKey` / `getOrThrow`),
/// projection helpers (`toMap` / `toMapping` / `toList`), and the
/// higher-order surface (`map` / `filter` / `fold`). The mutation
/// mutating-style helpers (`put` / `remove`) return new Maps rather
/// than mutating the receiver.
fn is_map_method_name(name : String) -> Bool {
  match name {
    "containsKey"
    // PKL-148: pkl:base Map.containsValue(v) is the value-side parallel
    // of `containsKey` — Apple Pkl's fixture `api/map.pkl` exercises it.
    | "containsValue"
    | "getOrNull"
    | "getOrThrow"
    | "toMap"
    | "toMapping"
    | "toList"
    | "map"
    | "mapKeys"
    | "mapValues"
    | "flatMap"
    | "filter"
    | "fold"
    | "put"
    | "remove"
    | "toDynamic"
    | "toTyped"
    // PKL-148bb: Map shares the every / any / none predicate trio with
    // Mapping (`classes/mapConstraints1` exercises every on a Map).
    | "every"
    | "any"
    | "none" => true
    _ => false
  }
}

///|
/// PKL-144: convert a `@json.Json` value into the corresponding Pkl
/// `Value`. `use_mapping` controls how JSON objects project — `true`
/// → `MappingValue` (key as `StringValue`, matching Apple Pkl's
/// `Parser { useMapping = true }` mode); `false` → `ObjectValue`
/// (one property per JSON entry, the Dynamic shape Apple Pkl uses
/// by default). Numbers convert to Int when integral, Float
/// otherwise — matches the upstream rule that JSON `42` evaluates
/// to a Pkl `Int` and `42.5` to a `Float`.
fn json_to_value(j : Json, use_mapping : Bool) -> Value {
  match j {
    Null => NullValue
    True => BoolValue(true)
    False => BoolValue(false)
    Number(d, ..) => {
      // PKL-150: Pkl's `Int` is i64. Widen the JSON Number → IntValue
      // round-trip to the full Int64 range; values outside fall back
      // to FloatValue. Previously clamped to i32.
      let truncated = d.to_int64().to_double()
      if truncated == d &&
        d >= -9223372036854775808.0 &&
        d <= 9223372036854775807.0 {
        IntValue(d.to_int64())
      } else {
        FloatValue(d)
      }
    }
    String(s) => StringValue(s)
    Array(items) => {
      let elements : Array[Value] = []
      for item in items {
        elements.push(json_to_value(item, use_mapping))
      }
      ListingValue(elements)
    }
    Object(map) =>
      if use_mapping {
        let entries : Array[ValueEntry] = []
        for k, v in map {
          entries.push({ key: StringValue(k), value: json_to_value(v, true) })
        }
        MappingValue(entries)
      } else {
        let members : Array[ValueMember] = []
        for k, v in map {
          members.push({
            name: k,
            value: json_to_value(v, false),
            source: None,
            annotations: [],
          })
        }
        ObjectValue(members)
      }
  }
}

///|
/// PKL-144 / PKL-145: read the `useMapping` slot from a pkl:json /
/// pkl:yaml Parser mirror. Each Parser is identified via the hidden
/// `__kind = ""` marker stamped by the
/// synthetic class (`reflect_kind` walks the same `__kind` member);
/// this helper only reads the toggle once the marker has been
/// matched.
fn parser_use_mapping(members : Array[ValueMember]) -> Bool {
  match lookup_member(members, "useMapping") {
    Some(BoolValue(b)) => b
    _ => false
  }
}

///|
fn parser_max_collection_aliases(members : Array[ValueMember]) -> Int {
  match lookup_member(members, "maxCollectionAliases") {
    Some(IntValue(n)) if n >= 0L => n.to_int()
    _ => 50
  }
}

///|
fn normalize_yaml_folded_block_chomping(source : String) -> String {
  // moonbit-community/yaml erases folded-vs-literal style in Yaml::String.
  // Apple Pkl's parser fixtures expect plain `>` to behave like `>-`, so
  // normalize the source before loading instead of post-processing every string.
  let buf = StringBuilder::new()
  let mut first = true
  for line in source.split("\n") {
    if first {
      first = false
    } else {
      buf.write_char('\n')
    }
    buf.write_string(rewrite_yaml_folded_block_indicator_line(line.to_owned()))
  }
  buf.to_string()
}

///|
fn rewrite_yaml_folded_block_indicator_line(line : String) -> String {
  let chars : Array[Char] = []
  for c in line {
    chars.push(c)
  }
  for i = 0; i < chars.length(); i = i + 1 {
    if chars[i] == '>' &&
      yaml_is_folded_block_indicator_position(chars, i) &&
      !yaml_block_indicator_has_chomping(chars, i) {
      let buf = StringBuilder::new()
      for j = 0; j < chars.length(); j = j + 1 {
        buf.write_char(chars[j])
        if j == i {
          buf.write_char('-')
        }
      }
      return buf.to_string()
    }
  }
  line
}

///|
fn yaml_is_folded_block_indicator_position(
  chars : Array[Char],
  index : Int,
) -> Bool {
  let mut prev = index - 1
  while prev >= 0 && (chars[prev] == ' ' || chars[prev] == '\t') {
    prev = prev - 1
  }
  prev < 0 || chars[prev] == ':' || chars[prev] == '-'
}

///|
fn yaml_block_indicator_has_chomping(chars : Array[Char], index : Int) -> Bool {
  let mut i = index + 1
  while i < chars.length() {
    let c = chars[i]
    if c == '+' || c == '-' {
      return true
    }
    if c >= '0' && c <= '9' {
      i = i + 1
      continue
    }
    if c == ' ' || c == '\t' || c == '#' {
      return false
    }
    return true
  }
  false
}

///|
/// PKL-146: convert a `moonbit-community/yaml` `Yaml` value into the
/// corresponding Pkl `Value`. `use_mapping` controls how YAML maps
/// project — `true` → `MappingValue` (key as `StringValue`); `false`
/// → `ObjectValue` (Dynamic shape, the default). Numbers: YAML
/// `Integer` becomes Pkl `Int`, while YAML `Real` remains Pkl
/// `Float` even when the numeric value is integral (`450.00`),
/// matching Apple Pkl parser converters. `BadValue` projects as
/// `NullValue` — the upstream `yaml` package emits it for parse
/// inputs the loader couldn't classify.
priv struct YamlAliasRefs {
  array_refs : Array[(Array[@yaml.Yaml], Int, Int64)]
  map_refs : Array[(Map[String, @yaml.Yaml], Int, Int64)]
  mut next_id : Int64
}

///|
/// moonbit-community/yaml resolves aliases by reusing the same collection
/// object but drops the anchor id itself. Pre-scan collection refs so only
/// repeated maps receive an internal alias tag; independent equal maps stay
/// structurally equal and untagged.
fn yaml_alias_refs_for_documents(docs : Array[@yaml.Yaml]) -> YamlAliasRefs {
  let refs = YamlAliasRefs::{ array_refs: [], map_refs: [], next_id: 1L }
  for doc in docs {
    collect_yaml_alias_refs(doc, refs)
  }
  refs
}

///|
fn collect_yaml_alias_refs(y : @yaml.Yaml, refs : YamlAliasRefs) -> Unit {
  match y {
    Array(items) => {
      note_yaml_array_ref(refs, items)
      for item in items {
        collect_yaml_alias_refs(item, refs)
      }
    }
    Map(map) => {
      note_yaml_map_ref(refs, map)
      for _, value in map {
        collect_yaml_alias_refs(value, refs)
      }
    }
    _ => ()
  }
}

///|
fn note_yaml_array_ref(refs : YamlAliasRefs, items : Array[@yaml.Yaml]) -> Unit {
  for i = 0; i < refs.array_refs.length(); i = i + 1 {
    let entry = refs.array_refs[i]
    if physical_equal(entry.0, items) {
      refs.array_refs[i] = (entry.0, entry.1 + 1, entry.2)
      return
    }
  }
  let id = refs.next_id
  refs.next_id = refs.next_id + 1L
  refs.array_refs.push((items, 1, id))
}

///|
fn note_yaml_map_ref(
  refs : YamlAliasRefs,
  map : Map[String, @yaml.Yaml],
) -> Unit {
  for i = 0; i < refs.map_refs.length(); i = i + 1 {
    let entry = refs.map_refs[i]
    if physical_equal(entry.0, map) {
      refs.map_refs[i] = (entry.0, entry.1 + 1, entry.2)
      return
    }
  }
  let id = refs.next_id
  refs.next_id = refs.next_id + 1L
  refs.map_refs.push((map, 1, id))
}

///|
fn yaml_map_alias_id(
  refs : YamlAliasRefs,
  map : Map[String, @yaml.Yaml],
) -> Int64? {
  for entry in refs.map_refs {
    if entry.1 > 1 && physical_equal(entry.0, map) {
      return Some(entry.2)
    }
  }
  None
}

///|
fn yaml_collection_alias_count(refs : YamlAliasRefs) -> Int {
  let mut count = 0
  for entry in refs.array_refs {
    if entry.1 > 1 {
      count = count + entry.1 - 1
    }
  }
  for entry in refs.map_refs {
    if entry.1 > 1 {
      count = count + entry.1 - 1
    }
  }
  count
}

///|
fn yaml_alias_member_name() -> String {
  local_member_name("__yamlAnchorId")
}

///|
fn attach_yaml_alias_member(
  members : Array[ValueMember],
  id : Int64?,
) -> Array[ValueMember] {
  match id {
    Some(anchor_id) => {
      let out : Array[ValueMember] = [
        {
          name: yaml_alias_member_name(),
          value: IntValue(anchor_id),
          source: None,
          annotations: [],
        },
      ]
      for field in members {
        out.push(field)
      }
      out
    }
    None => members
  }
}

///|
fn yaml_to_value_with_aliases(
  y : @yaml.Yaml,
  use_mapping : Bool,
  refs : YamlAliasRefs,
) -> Value {
  match y {
    Null => NullValue
    Boolean(b) => BoolValue(b)
    Integer(i) => IntValue(i)
    Real(d, ..) => FloatValue(d)
    String(s) =>
      match yaml_v12_try_decode_binary_string(s) {
        Some(v) => v
        None => StringValue(s)
      }
    Array(items) => {
      let elements : Array[Value] = []
      for item in items {
        elements.push(yaml_to_value_with_aliases(item, use_mapping, refs))
      }
      ListingValue(elements)
    }
    Map(map) =>
      if yaml_v12_map_is_set(map) {
        // PKL-153c: `!!set` rewritten into a sentinel-marked map by
        // `yaml_v12_rewrite_set`. Project to a Listing of the (string)
        // keys to mirror Apple Pkl's `!!set` → Listing-of-keys rendering.
        let elements : Array[Value] = []
        for k, _ in map {
          if k == yaml_v12_set_marker_member {
            continue
          }
          elements.push(StringValue(k))
        }
        ListingValue(elements)
      } else {
        // PKL-153d: a YAML map may contain explicit-key entries that
        // we pre-rewrote into sentinel string keys. If any entry's
        // raw key carries the sentinel prefix we project the WHOLE
        // map as a `MappingValue` (since `ObjectValue` member names
        // can't hold non-string keys), decoding each sentinel back
        // into the original value.
        let mut has_complex = false
        for k, _ in map {
          if yaml_v12_try_decode_complex_key(k, refs, use_mapping) is Some(_) {
            has_complex = true
            break
          }
        }
        if use_mapping || has_complex {
          let entries : Array[ValueEntry] = []
          for k, v in map {
            let key_value = match
              yaml_v12_try_decode_complex_key(k, refs, use_mapping) {
              Some(decoded) => decoded
              None => StringValue(k)
            }
            entries.push({
              key: key_value,
              value: yaml_to_value_with_aliases(v, use_mapping, refs),
            })
          }
          MappingValue(entries)
        } else {
          let members : Array[ValueMember] = []
          for k, v in map {
            members.push({
              name: k,
              value: yaml_to_value_with_aliases(v, false, refs),
              source: None,
              annotations: [],
            })
          }
          ObjectValue(
            attach_yaml_alias_member(members, yaml_map_alias_id(refs, map)),
          )
        }
      }
    BadValue => NullValue
  }
}

///|
/// PKL-119b: materialize an IntSeq into an Array[Value] of Int
/// elements. Empty when `step > 0 && start > end` or
/// `step < 0 && start < end`; `step == 0` would be rejected by the
/// caller before reaching here (and is treated as empty here too as
/// a defensive guard).
fn intseq_materialize(
  start : Int64,
  end_v : Int64,
  step : Int64,
) -> Array[Value] {
  // PKL-148am / PKL-150: iterate in Int64 so `start + step` cannot wrap
  // even at the full Int range. Directional check (next moved the wrong
  // way) catches the boundary case where step is 0 (caller rejects
  // upstream) or somehow non-monotone.
  let result : Array[Value] = []
  if step > 0L {
    let mut i = start
    while i <= end_v {
      result.push(IntValue(i))
      let next = i + step
      if next <= i {
        break
      }
      i = next
    }
  } else if step < 0L {
    let mut i = start
    while i >= end_v {
      result.push(IntValue(i))
      let next = i + step
      if next >= i {
        break
      }
      i = next
    }
  }
  result
}

///|
fn bytes_materialize(bytes : Bytes) -> Array[Value] {
  let result : Array[Value] = []
  for i = 0; i < bytes.length(); i = i + 1 {
    result.push(IntValue(bytes[i].to_int().to_int64()))
  }
  result
}

///|
/// Number of elements an IntSeq with the given start/end/step would
/// produce. Computed in Int64 so that ranges that span the full Int32
/// width (`IntSeq(math.minInt, math.maxInt)`) don't overflow.
fn intseq_length(start : Int64, end_v : Int64, step : Int64) -> Int64 {
  let int64_max = 9223372036854775807L
  let int64_min = 0L - int64_max - 1L
  let saturating_distance = fn(lo : Int64, hi : Int64) -> Int64 {
    if lo < 0L && hi > 0L {
      if lo == int64_min {
        return int64_max
      }
      let left = 0L - lo
      if int64_max - left < hi {
        int64_max
      } else {
        left + hi
      }
    } else {
      hi - lo
    }
  }
  let saturating_len = fn(distance : Int64, positive_step : Int64) -> Int64 {
    let q = distance / positive_step
    if q >= int64_max {
      int64_max
    } else {
      q + 1L
    }
  }
  if step > 0L {
    if start > end_v {
      return 0L
    }
    return saturating_len(saturating_distance(start, end_v), step)
  }
  if step < 0L {
    if start < end_v {
      return 0L
    }
    return saturating_len(saturating_distance(end_v, start), 0L - step)
  }
  0L
}

///|
/// Pkl value equality for collection membership and unordered content
/// comparisons. MoonBit's derived equality is fine for scalars, but
/// containers need Apple Pkl's visible-member / order-insensitive rules.
fn values_equal(a : Value, b : Value) -> Bool {
  let a = force_eval_thunk(a)
  let b = force_eval_thunk(b)
  match (a, b) {
    (ObjectValue(xs), ObjectValue(ys)) => object_values_equal(xs, ys)
    (MapValue(xs), MapValue(ys)) => map_entries_equal(xs, ys)
    (MappingValue(xs), MappingValue(ys)) => map_entries_equal(xs, ys)
    (DefaultedMappingValue(_, xs, _), MappingValue(ys))
    | (MappingValue(xs), DefaultedMappingValue(_, ys, _))
    | (DefaultedMappingValue(_, xs, _), DefaultedMappingValue(_, ys, _)) =>
      map_entries_equal(xs, ys)
    (SetValue(xs), SetValue(ys)) => set_values_equal(xs, ys)
    (ListingValue(xs), ListingValue(ys)) => value_arrays_equal(xs, ys)
    (DefaultedListingValue(_, xs, _), ListingValue(ys))
    | (ListingValue(xs), DefaultedListingValue(_, ys, _))
    | (DefaultedListingValue(_, xs, _), DefaultedListingValue(_, ys, _)) =>
      value_arrays_equal(xs, ys)
    (ListValue(xs), ListValue(ys)) => value_arrays_equal(xs, ys)
    (IntSeqValue(s1, e1, st1), IntSeqValue(s2, e2, st2)) =>
      intseq_value_equal(s1, e1, st1, s2, e2, st2)
    // Function equality is identity equality. Comparing the complete
    // FunctionValue payload structurally walks its captured environment;
    // a memoized property thunk can legitimately make that environment
    // cyclic (function -> env -> thunk -> computation -> env). The stable
    // function id is both Pkl's observable contract and the cycle-safe key.
    (FunctionValue(_, _, _, _, left_id), FunctionValue(_, _, _, _, right_id)) =>
      left_id == right_id
    _ => a == b
  }
}

///|
fn value_arrays_equal(xs : Array[Value], ys : Array[Value]) -> Bool {
  if xs.length() != ys.length() {
    return false
  }
  for i = 0; i < xs.length(); i = i + 1 {
    if !values_equal(xs[i], ys[i]) {
      return false
    }
  }
  true
}

///|
/// Visible-member equality for ObjectValue operands. Compares only
/// rendered (non-hidden) properties and ignores order so
/// `{foo=1; bar=2} == {bar=2; foo=1}` agrees with Apple Pkl.
fn object_values_equal(
  xs : Array[ValueMember],
  ys : Array[ValueMember],
) -> Bool {
  if is_reference_value_members(xs) || is_reference_value_members(ys) {
    return is_reference_value_members(xs) &&
      is_reference_value_members(ys) &&
      reference_values_equal(xs, ys)
  }
  match (reflect_kind(xs), reflect_kind(ys)) {
    (Some("Class"), Some("Class")) | (Some("TypeAlias"), Some("TypeAlias")) => {
      let x_name = match
        lookup_member(xs, hidden_member_name("__qualified_name")) {
        Some(StringValue(name)) => Some(name)
        _ =>
          match lookup_member(xs, "name") {
            Some(StringValue(name)) => Some(name)
            _ => None
          }
      }
      let y_name = match
        lookup_member(ys, hidden_member_name("__qualified_name")) {
        Some(StringValue(name)) => Some(name)
        _ =>
          match lookup_member(ys, "name") {
            Some(StringValue(name)) => Some(name)
            _ => None
          }
      }
      match (x_name, y_name) {
        (Some(x), Some(y)) => return x == y
        _ => ()
      }
    }
    _ => ()
  }
  if reflect_kind(xs) is Some("Module") && reflect_kind(ys) is Some("Module") {
    match (lookup_member(xs, "uri"), lookup_member(ys, "uri")) {
      (Some(StringValue(x_uri)), Some(StringValue(y_uri))) =>
        return x_uri == y_uri
      _ => ()
    }
  }
  match
    (
      hidden_string_member(xs, "__module_path"),
      hidden_string_member(ys, "__module_path"),
    ) {
    (Some(x_path), Some(y_path)) if x_path != y_path => return false
    _ => ()
  }
  // PKL-148bh: class identity is part of equality — `new Person {}`
  // and `new Person2 {}` carry distinct `@hidden$__class` tags so
  // they don't compare equal even though the visible-member arrays
  // are both empty. An untagged ObjectValue (bare object-literal
  // body like `(obj1) {}`) compares as `Dynamic` so `(obj1) {} ==
  // new Dynamic { foo = 1 }` still returns true when the visible
  // members align.
  let class_xs = match find_object_class_tag(xs) {
    Some(s) => s
    None => "Dynamic"
  }
  let class_ys = match find_object_class_tag(ys) {
    Some(s) => s
    None => "Dynamic"
  }
  // Imported aliases and the declaring module can spell the same class
  // differently (`BaseModule.MyAnn` vs `MyAnn`). Class-tag matching is
  // already used by `is` and annotation dispatch for this reason.
  if !name_matches_class_tag(class_xs, class_ys) {
    return false
  }
  let visible_xs = visible_members(xs)
  let visible_ys = visible_members(ys)
  if visible_xs.length() != visible_ys.length() {
    return false
  }
  for x in visible_xs {
    let mut matched = false
    for y in visible_ys {
      if y.name == x.name && values_equal(y.value, x.value) {
        matched = true
        break
      }
    }
    if !matched {
      return false
    }
  }
  true
}

///|
fn hidden_string_member(members : Array[ValueMember], name : String) -> String? {
  let hidden = hidden_member_name(name)
  for value_member in members {
    if value_member.name == hidden {
      match value_member.value {
        StringValue(value) => return Some(value)
        _ => return None
      }
    }
  }
  None
}

///|
/// Map / Mapping content equality — key/value order is immaterial; two
/// entries with the same key/value pair regardless of position match.
fn map_entries_equal(xs : Array[ValueEntry], ys : Array[ValueEntry]) -> Bool {
  if xs.length() != ys.length() {
    return false
  }
  let consumed : Array[Bool] = Array::make(ys.length(), false)
  for x in xs {
    let mut matched = false
    for j = 0; j < ys.length(); j = j + 1 {
      if !consumed[j] &&
        values_equal(ys[j].key, x.key) &&
        values_equal(ys[j].value, x.value) {
        consumed[j] = true
        matched = true
        break
      }
    }
    if !matched {
      return false
    }
  }
  true
}

///|
/// Multiset equality for SetValue operands — Apple Pkl treats two Sets
/// as equal when they hold the same elements regardless of insertion
/// order. Quadratic, but Sets in practice are small.
fn set_values_equal(xs : Array[Value], ys : Array[Value]) -> Bool {
  if xs.length() != ys.length() {
    return false
  }
  let consumed : Array[Bool] = Array::make(ys.length(), false)
  for x in xs {
    let mut matched = false
    for j = 0; j < ys.length(); j = j + 1 {
      if !consumed[j] && values_equal(ys[j], x) {
        consumed[j] = true
        matched = true
        break
      }
    }
    if !matched {
      return false
    }
  }
  true
}

///|
/// PKL-119be (IntSeq equality follow-up): two IntSeqs are equal when
/// they produce the same Int sequence. Compute lengths without
/// materializing — for arithmetic progressions equality reduces to
/// matching length, start, and step (step doesn't matter when len <= 1).
fn intseq_value_equal(
  start_a : Int64,
  end_a : Int64,
  step_a : Int64,
  start_b : Int64,
  end_b : Int64,
  step_b : Int64,
) -> Bool {
  if start_a == start_b && end_a == end_b && step_a == step_b {
    return true
  }
  let len_a = intseq_length(start_a, end_a, step_a)
  let len_b = intseq_length(start_b, end_b, step_b)
  if len_a != len_b {
    return false
  }
  if len_a == 0 {
    return true
  }
  if start_a != start_b {
    return false
  }
  if len_a == 1 {
    return true
  }
  step_a == step_b
}