///|
// Opaque, immutable generated-set descriptor. Query coefficients are rounded
// binary64 values, hence exactly representable as Double; exact comparisons
// reproduce Python set membership without arbitrary-key weak-equality rules.
priv enum LogSublabelCache {
  Explicit(Array[Double])
  Arange(Int64)
  Geom(AsinhGridLine, Double)
}

///|
fn log_cache_explicit(values : Array[Double]) -> LogSublabelCache {
  LogSublabelCache::Explicit(values)
}

///|
fn log_cache_geom_point(
  line : AsinhGridLine,
  base : Double,
  index : Int64,
) -> Double {
  if index == 0L {
    return 1.0
  }
  if index == line.count - 1L && line.requested > 1L {
    return base
  }
  scalar_math_pow10(line.at(index))
}

///|
fn log_cache_at(cache : LogSublabelCache, index : Int64) -> Double {
  match cache {
    Explicit(values) => values[index.to_int()]
    Arange(_) => numeric_arange_fill(1.0, 1.0, index.to_double())
    Geom(line, base) =>
      scalar_math_rint(log_cache_geom_point(line, base, index))
  }
}

///|
fn log_cache_contains(cache : LogSublabelCache, value : Double) -> Bool {
  match cache {
    Explicit(values) => values.contains(value)
    Arange(length) => {
      let first = asinh_grid_boundary(length, index => {
        log_cache_at(cache, index) >= value
      })
      first < length && log_cache_at(cache, first) == value
    }
    Geom(line, _) => {
      if line.count == 0L {
        return false
      }
      if value == log_cache_at(cache, line.count - 1L) {
        return true
      }
      let length = if line.requested > 1L {
        line.count - 1L
      } else {
        line.count
      }
      let first = asinh_grid_boundary(length, index => {
        log_cache_at(cache, index) >= value
      })
      first < length && log_cache_at(cache, first) == value
    }
  }
}

///|
fn log_cache_values(
  cache : LogSublabelCache,
) -> Array[Double] raise @core.DataError {
  let result : Array[Double] = []
  match cache {
    Explicit(values) => {
      for value in values {
        if !result.contains(value) {
          result.push(value)
        }
      }
      result.sort()
    }
    Arange(length) => {
      // Until 2^53 all entries are distinct. A longer arange already contains
      // more than Int32.MAX distinct entries, regardless of later plateaus.
      if length > 2147483647L {
        raise log_format_error(
          "ResourceError: log sublabel snapshot exceeds Array addressability",
        )
      }
      for index = 0L; index < length; index = index + 1L {
        result.push(log_cache_at(cache, index))
      }
    }
    Geom(line, base) => {
      // Only a low-coefficient prefix is used for this addressability proof.
      // For base>2^40, n>=2^39 and L=RN(log10(base))<19. The next phase
      // increment is bounded by 20/(n-1)+20*2^-52*n/(n-1)+2^-48 <2^-34.
      // Below coefficient 2^32+1 this gives a rounded-power increment <0.577,
      // hence a rounded-integer jump at most1. The monotone prefix therefore
      // contains every integer1..2^31+1: more than Array.MAX distinct values.
      // There is deliberately no claim about jumps near the large endpoint.
      if base > 1099511627776.0 {
        raise log_format_error(
          "ResourceError: log sublabel snapshot exceeds Array addressability",
        )
      }
      let length = if line.requested > 1L {
        line.count - 1L
      } else {
        line.count
      }
      let mut index = 0L
      while index < length {
        let value = log_cache_at(cache, index)
        if result.length() == 2147483647 {
          raise log_format_error(
            "ResourceError: log sublabel snapshot exceeds Array addressability",
          )
        }
        result.push(value)
        // Exponential search keeps common short plateaus local; the final
        // binary search still respects the complete logical shape.
        let mut distance = 1L
        while distance < length - index &&
              log_cache_at(cache, index + distance) <= value {
          distance = Int64::min(distance * 2L, length - index)
        }
        let after = asinh_grid_boundary(
          Int64::min(distance + 1L, length - index),
          offset => log_cache_at(cache, index + offset) > value,
        )
        index += after
      }
      if line.count > 0L && line.requested > 1L {
        let last = scalar_math_rint(base)
        if !result.contains(last) {
          if result.length() == 2147483647 {
            raise log_format_error(
              "ResourceError: log sublabel snapshot exceeds Array addressability",
            )
          }
          result.push(last)
          result.sort()
        }
      }
    }
  }
  result
}

///|
fn log_cache_arange(base : Double) -> LogSublabelCache raise @core.DataError {
  let stop = base + 1.0
  let length = (stop - 1.0).ceil()
  if length.is_nan() {
    raise log_format_error("ValueError: arange: cannot compute length")
  }
  if length.is_inf() ||
    length >= 9223372036854775808.0 ||
    length < -9223372036854775808.0 {
    raise log_format_error("ValueError: Maximum allowed size exceeded")
  }
  if length >= 1152921504606846976.0 {
    raise log_format_error(
      "ValueError: array is too big; `arr.size * arr.dtype.itemsize` is larger than the maximum possible size.",
    )
  }
  LogSublabelCache::Arange(if length <= 0.0 { 0L } else { length.to_int64() })
}

///|
fn log_cache_geom(base : Double) -> LogSublabelCache raise @core.DataError {
  // geomspace checks zero, then computes its log endpoints before linspace
  // validates num. Native RuntimeWarning routing remains a host boundary.
  if base == 0.0 {
    raise log_format_error("ValueError: Geometric sequence cannot include zero")
  }
  let integer = scalar_numeric_integer(ScalarNumber::Number(base))
  let two = @bigint.BigInt::from_int(2)
  let requested = log_format_floor_div(integer, two) +
    @bigint.BigInt::from_int(1)
  if requested < @bigint.BigInt::from_int(0) {
    raise log_format_error(
      "ValueError: Number of samples, " +
      scalar_percent_integer_str(requested) +
      ", must be non-negative.",
    )
  }
  let count = index_round_binary(requested, 0)
  if count >= 9223372036854775808.0 {
    raise log_format_error("ValueError: Maximum allowed size exceeded")
  }
  if count >= 1152921504606846976.0 {
    raise log_format_error(
      "ValueError: array is too big; `arr.size * arr.dtype.itemsize` is larger than the maximum possible size.",
    )
  }
  // Negative-base source branches that reach a nonnegative count have only
  // 0 or 1 points; the forced initial endpoint is the sole surviving value.
  if requested <= @bigint.BigInt::from_int(1) {
    return log_cache_explicit(if requested.is_zero() { [] } else { [1.0] })
  }
  LogSublabelCache::Geom(
    asinh_grid_line(0.0, asinh_math_log10(base), requested.to_int64()),
    base,
  )
}