// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// A one-dimensional range between two numbers, `min` at or below `max`.
///
/// It is the one-dimensional case of an axis-aligned box, and what a timeline,
/// an exposure or a depth range is. An empty interval — no numbers at all — is
/// the one with `min` above `max`, which is how the union of nothing starts and
/// how `empty` builds it; every operation here treats it as the identity the
/// set operations expect.
///
/// Values are immutable.
#derive(Eq)
pub(all) struct Interval {
  /// The low end, inclusive.
  min : Float
  /// The high end, inclusive.
  max : Float
}

///|
pub extend Interval with Eq::{equal, not_equal}

///|
/// The interval from `min` to `max`; the two are not reordered, so passing them
/// the other way round gives the empty interval.
pub fn Interval::new(min : Float, max : Float) -> Interval {
  { min, max, }
}

///|
/// The interval with no numbers in it, which every union starts from.
pub fn Interval::empty() -> Interval {
  { min: 3.4028234663852886e38, max: -3.4028234663852886e38, }
}

///|
/// Whether the interval holds no numbers: `min` above `max`.
pub fn Interval::is_empty(self : Interval) -> Bool {
  self.min > self.max
}

///|
/// The distance from `min` to `max`, or zero for the empty interval.
pub fn Interval::size(self : Interval) -> Float {
  if self.is_empty() {
    0.0
  } else {
    self.max - self.min
  }
}

///|
/// The midpoint of the interval.
pub fn Interval::center(self : Interval) -> Float {
  let half : Float = 0.5
  (self.min + self.max) * half
}

///|
/// Whether `value` lies in the interval, ends included.
pub fn Interval::contains(self : Interval, value : Float) -> Bool {
  value >= self.min && value <= self.max
}

///|
/// Whether the two intervals share at least one point.
pub fn Interval::intersects(self : Interval, other : Interval) -> Bool {
  !self.is_empty() &&
  !other.is_empty() &&
  self.min <= other.max &&
  other.min <= self.max
}

///|
/// The overlap of the two, or `None` when they do not meet.
pub fn Interval::intersection(self : Interval, other : Interval) -> Interval? {
  if self.intersects(other) {
    Some(Interval::new(self.min.max(other.min), self.max.min(other.max)))
  } else {
    None
  }
}

///|
/// The smallest interval holding both; the empty interval is the identity.
pub fn Interval::union(self : Interval, other : Interval) -> Interval {
  if self.is_empty() {
    other
  } else if other.is_empty() {
    self
  } else {
    Interval::new(self.min.min(other.min), self.max.max(other.max))
  }
}

///|
/// The interval grown to include `value`; the empty interval becomes `[value,
/// value]`.
pub fn Interval::extend_by(self : Interval, value : Float) -> Interval {
  if self.is_empty() {
    Interval::new(value, value)
  } else {
    Interval::new(self.min.min(value), self.max.max(value))
  }
}

///|
/// The interval grown by `amount` at both ends; a negative amount shrinks it.
pub fn Interval::expand(self : Interval, amount : Float) -> Interval {
  Interval::new(self.min - amount, self.max + amount)
}

///|
/// Whether both ends are finite.
pub fn Interval::is_finite(self : Interval) -> Bool {
  !(self.min.is_nan() ||
  self.min.is_inf() ||
  self.max.is_nan() ||
  self.max.is_inf())
}

///|
/// Whether the two intervals agree end for end to within `eps`.
pub fn Interval::approx(
  self : Interval,
  other : Interval,
  eps? : Float,
) -> Bool {
  approx(self.min, other.min, eps?) && approx(self.max, other.max, eps?)
}

///|
pub impl Show for Interval with fn output(self, logger) {
  logger.write_string("Interval(\{self.min}, \{self.max})")
}

///|
pub extend Interval with Show::{output, to_string}

///|
pub impl Debug for Interval with fn to_repr(self) {
  @debug.Repr::ctor("Interval", [
    (Some("min"), @debug.Repr::float(self.min)),
    (Some("max"), @debug.Repr::float(self.max)),
  ])
}

///|
pub extend Interval with Debug::{to_repr}