// Comparison / equality operators implementing ECMA-262 §7.2 (abstract
// equality, strict equality, abstract relational comparison).

///|
/// ES `x === y` — Strict Equality Comparison. Same-type + same-value.
///
/// - Different types → false. Exception (M1 pragmatic decision): `Int32(n)`
///   and `Number(n as Double)` compare equal, because our Int32 fast path
///   is a storage detail, not a JS-visible type. JS only has "number" as a
///   type at the language level.
/// - `NaN` never equals anything (including itself).
/// - `+0 === -0` → true (both are numeric zero; JS spec).
/// - `undefined === undefined`, `null === null` → true.
/// - Strings: character-by-character equality.
/// - Booleans: same boolean.
/// - Objects / Functions / NativeFns: identity (physical_equal).
fn js_strict_eq(x : @value.JSValue, y : @value.JSValue) -> Bool {
  match (x, y) {
    (Undefined, Undefined) => true
    (Null, Null) => true
    (Bool(a), Bool(b)) => a == b
    // Number-family cross-comparison: Int32 vs Number, Number vs Int32,
    // Int32 vs Int32, Number vs Number.
    (Int32(a), Int32(b)) => a == b
    (Int32(a), Number(b)) => a.to_double() == b
    (Number(a), Int32(b)) => a == b.to_double()
    (Number(a), Number(b)) =>
      // JS spec: NaN !== NaN. Double::equal already respects this.
      a == b
    (Str(a), Str(b)) => a == b
    (Object(a), Object(b)) => physical_equal(a, b)
    (Function(a), Function(b)) => physical_equal(a, b)
    (NativeFn(a), NativeFn(b)) => physical_equal(a, b)
    _ => false
  }
}

///|
/// ES `x == y` — Abstract Equality Comparison (aka "loose equality").
///
/// Rules (ECMA-262 7.2.14):
/// - Same type → StrictEquals.
/// - null == undefined (both directions) → true.
/// - Number vs String → ToNumber(String) then StrictEquals.
/// - Bool → ToNumber(Bool) then re-run.
/// - Object vs primitive → ToPrimitive(Object) then re-run. M1 stub: since
///   ToPrimitive on an Object returns Undefined (no method dispatch yet),
///   the recursion terminates: Object == Undefined is Object == null? No —
///   only null / undefined satisfy the "== primitive" branch. So M1 falls
///   through to `false` for Object == primitive comparisons where the
///   primitive isn't null/undefined. Documented deviation.
fn js_loose_eq(x : @value.JSValue, y : @value.JSValue) -> Bool {
  match (x, y) {
    // Same-type: delegate to strict.
    (Undefined, Undefined) => true
    (Null, Null) => true
    (Bool(_), Bool(_)) | (Str(_), Str(_)) => js_strict_eq(x, y)
    // null / undefined cross-equal.
    (Null, Undefined) | (Undefined, Null) => true
    // Numeric family: Int32 and Number are both "number" per JS semantics.
    (Int32(_), Int32(_))
    | (Int32(_), Number(_))
    | (Number(_), Int32(_))
    | (Number(_), Number(_)) => js_strict_eq(x, y)
    // Number/Int32 vs String → convert String to Number.
    (Int32(_), Str(_)) | (Number(_), Str(_)) =>
      js_strict_eq(x, Number(to_number(y)))
    (Str(_), Int32(_)) | (Str(_), Number(_)) =>
      js_strict_eq(Number(to_number(x)), y)
    // Bool → ToNumber, re-run.
    (Bool(_), _) => js_loose_eq(Number(to_number(x)), y)
    (_, Bool(_)) => js_loose_eq(x, Number(to_number(y)))
    // Object == Object → identity (same as strict).
    (Object(a), Object(b)) => physical_equal(a, b)
    (Function(a), Function(b)) => physical_equal(a, b)
    (NativeFn(a), NativeFn(b)) => physical_equal(a, b)
    // Object == null / undefined per spec: false. Only "IsHTMLDDA" hosts
    // permit true; we don't have those.
    (Object(_), Null)
    | (Null, Object(_))
    | (Object(_), Undefined)
    | (Undefined, Object(_)) => false
    // Object vs Number / String: would call ToPrimitive(Object) then re-run.
    // M1 without method dispatch → false. Documented.
    _ => false
  }
}

///|
/// ES `x < y` (with `left_first` = true) — Abstract Relational Comparison
/// as per ECMA-262 7.2.13, returning `Some(true) | Some(false) | None`,
/// where `None` corresponds to the spec's `undefined` result (NaN case).
///
/// - Both strings → lexicographic (UTF-16 code-unit-wise).
/// - Otherwise → ToNumber on both. If either is NaN → None.
fn abstract_relational(x : @value.JSValue, y : @value.JSValue) -> Bool? {
  // If both are strings, compare lexicographically (UTF-16 code units).
  match (x, y) {
    (Str(a), Str(b)) => Some(a < b)
    _ => {
      let a = to_number(x)
      let b = to_number(y)
      if a.is_nan() || b.is_nan() {
        None
      } else {
        Some(a < b)
      }
    }
  }
}

///|
/// JS `x < y`. Returns Bool; NaN → false.
fn js_lt(x : @value.JSValue, y : @value.JSValue) -> Bool {
  match abstract_relational(x, y) {
    Some(b) => b
    None => false
  }
}

///|
/// JS `x > y`. Uses abstract relational with operands swapped.
fn js_gt(x : @value.JSValue, y : @value.JSValue) -> Bool {
  match abstract_relational(y, x) {
    Some(b) => b
    None => false
  }
}

///|
/// JS `x <= y`. Defined as `!(y < x)` (with NaN → false override).
fn js_le(x : @value.JSValue, y : @value.JSValue) -> Bool {
  match abstract_relational(y, x) {
    Some(b) => !b
    None => false
  }
}

///|
/// JS `x >= y`. Defined as `!(x < y)` with NaN → false.
fn js_ge(x : @value.JSValue, y : @value.JSValue) -> Bool {
  match abstract_relational(x, y) {
    Some(b) => !b
    None => false
  }
}