/**
 * @module db/stableEqual
 * @description Key-order-insensitive deep equality, used by {@link Brainy.diff}
 * to decide whether an id present at both endpoints actually *changed* value.
 *
 * A naive `JSON.stringify(a) === JSON.stringify(b)` is wrong for this: object key
 * order is insignificant for stored-metadata equality, but `stringify` is
 * order-sensitive, so `{a:1,b:2}` and `{b:2,a:1}` would mis-compare as changed and
 * a no-op re-write would show up as a spurious `modified`. This walks both values
 * structurally — object keys compared as a set, array elements compared in order
 * (arrays ARE order-significant) — over the JSON-shaped values that live in
 * generation records.
 */

/**
 * @description Deep-equal two JSON-shaped values, insensitive to object key order.
 * @param a - First value.
 * @param b - Second value.
 * @returns `true` if structurally equal (key order ignored; array order honored;
 *   `NaN` equals `NaN`).
 * @example
 * stableDeepEqual({ a: 1, b: 2 }, { b: 2, a: 1 }) // → true
 * stableDeepEqual([1, 2], [2, 1])                 // → false (order matters)
 */
export function stableDeepEqual(a: unknown, b: unknown): boolean {
  if (a === b) return true

  // NaN is never === itself, but for a stable value comparison NaN equals NaN.
  if (typeof a === 'number' && typeof b === 'number') {
    return Number.isNaN(a) && Number.isNaN(b)
  }

  // Distinct primitives, or exactly one of them null/non-object → not equal
  // (the `a === b` above already returned true for equal primitives and for
  // both-null).
  if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
    return false
  }

  const aIsArr = Array.isArray(a)
  const bIsArr = Array.isArray(b)
  if (aIsArr !== bIsArr) return false

  if (aIsArr) {
    const aa = a as unknown[]
    const ba = b as unknown[]
    if (aa.length !== ba.length) return false
    for (let i = 0; i < aa.length; i++) {
      if (!stableDeepEqual(aa[i], ba[i])) return false
    }
    return true
  }

  const ao = a as Record<string, unknown>
  const bo = b as Record<string, unknown>
  const aKeys = Object.keys(ao)
  const bKeys = Object.keys(bo)
  if (aKeys.length !== bKeys.length) return false
  for (const k of aKeys) {
    if (!Object.prototype.hasOwnProperty.call(bo, k)) return false
    if (!stableDeepEqual(ao[k], bo[k])) return false
  }
  return true
}
