// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 16.13.0
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually

"use strict";

// Confirm functionality of `util.isDeepStrictEqual()`.

require("../common");

const assert = require("assert");
const util = require("util");

class MyDate extends Date {
  constructor(...args) {
    super(...args);
    this[0] = "1";
  }
}

class MyRegExp extends RegExp {
  constructor(...args) {
    super(...args);
    this[0] = "1";
  }
}

{
  const arr = new Uint8Array([120, 121, 122, 10]);
  const buf = Buffer.from(arr);
  // They have different [[Prototype]]
  assert.strictEqual(util.isDeepStrictEqual(arr, buf), false);

  const buf2 = Buffer.from(arr);
  buf2.prop = 1;

  assert.strictEqual(util.isDeepStrictEqual(buf2, buf), false);

  const arr2 = new Uint8Array([120, 121, 122, 10]);
  arr2.prop = 5;
  assert.strictEqual(util.isDeepStrictEqual(arr, arr2), false);
}

{
  const date = new Date("2016");

  const date2 = new MyDate("2016");

  // deepStrictEqual checks own properties
  assert.strictEqual(util.isDeepStrictEqual(date, date2), false);
  assert.strictEqual(util.isDeepStrictEqual(date2, date), false);
}

{
  const re1 = new RegExp("test");
  const re2 = new MyRegExp("test");

  // deepStrictEqual checks all properties
  assert.strictEqual(util.isDeepStrictEqual(re1, re2), false);
}

{
  // For these cases, deepStrictEqual should throw.
  const similar = new Set([
    { 0: "1" }, // Object
    { 0: 1 }, // Object
    new String("1"), // Object
    ["1"], // Array
    [1], // Array
    new MyDate("2016"), // Date with this[0] = '1'
    new MyRegExp("test"), // RegExp with this[0] = '1'
    new Int8Array([1]), // Int8Array
    new Uint8Array([1]), // Uint8Array
    new Int16Array([1]), // Int16Array
    new Uint16Array([1]), // Uint16Array
    new Int32Array([1]), // Int32Array
    new Uint32Array([1]), // Uint32Array
    Buffer.from([1]), // Buffer
  ]);

  for (const a of similar) {
    for (const b of similar) {
      if (a !== b) {
        assert.strictEqual(util.isDeepStrictEqual(a, b), false);
      }
    }
  }
}

function utilIsDeepStrict(a, b) {
  assert.strictEqual(util.isDeepStrictEqual(a, b), true);
  assert.strictEqual(util.isDeepStrictEqual(b, a), true);
}
function notUtilIsDeepStrict(a, b) {
  assert.strictEqual(util.isDeepStrictEqual(a, b), false);
  assert.strictEqual(util.isDeepStrictEqual(b, a), false);
}

// es6 Maps and Sets
utilIsDeepStrict(new Set(), new Set());
utilIsDeepStrict(new Map(), new Map());

utilIsDeepStrict(new Set([1, 2, 3]), new Set([1, 2, 3]));
notUtilIsDeepStrict(new Set([1, 2, 3]), new Set([1, 2, 3, 4]));
notUtilIsDeepStrict(new Set([1, 2, 3, 4]), new Set([1, 2, 3]));
utilIsDeepStrict(new Set(["1", "2", "3"]), new Set(["1", "2", "3"]));
utilIsDeepStrict(
  new Set([
    [1, 2],
    [3, 4],
  ]),
  new Set([
    [3, 4],
    [1, 2],
  ])
);

{
  const a = [1, 2];
  const b = [3, 4];
  const c = [1, 2];
  const d = [3, 4];

  utilIsDeepStrict(
    { a: a, b: b, s: new Set([a, b]) },
    { a: c, b: d, s: new Set([d, c]) }
  );
}

utilIsDeepStrict(
  new Map([
    [1, 1],
    [2, 2],
  ]),
  new Map([
    [1, 1],
    [2, 2],
  ])
);
utilIsDeepStrict(
  new Map([
    [1, 1],
    [2, 2],
  ]),
  new Map([
    [2, 2],
    [1, 1],
  ])
);
notUtilIsDeepStrict(
  new Map([
    [1, 1],
    [2, 2],
  ]),
  new Map([
    [1, 2],
    [2, 1],
  ])
);
notUtilIsDeepStrict(
  new Map([
    [[1], 1],
    [{}, 2],
  ]),
  new Map([
    [[1], 2],
    [{}, 1],
  ])
);

notUtilIsDeepStrict(new Set([1]), [1]);
notUtilIsDeepStrict(new Set(), []);
notUtilIsDeepStrict(new Set(), {});

notUtilIsDeepStrict(new Map([["a", 1]]), { a: 1 });
notUtilIsDeepStrict(new Map(), []);
notUtilIsDeepStrict(new Map(), {});

notUtilIsDeepStrict(new Set(["1"]), new Set([1]));

notUtilIsDeepStrict(new Map([["1", "a"]]), new Map([[1, "a"]]));
notUtilIsDeepStrict(new Map([["a", "1"]]), new Map([["a", 1]]));
notUtilIsDeepStrict(new Map([["a", "1"]]), new Map([["a", 2]]));

utilIsDeepStrict(new Set([{}]), new Set([{}]));

// Ref: https://github.com/nodejs/node/issues/13347
notUtilIsDeepStrict(
  new Set([{ a: 1 }, { a: 1 }]),
  new Set([{ a: 1 }, { a: 2 }])
);
notUtilIsDeepStrict(
  new Set([{ a: 1 }, { a: 1 }, { a: 2 }]),
  new Set([{ a: 1 }, { a: 2 }, { a: 2 }])
);
notUtilIsDeepStrict(
  new Map([
    [{ x: 1 }, 5],
    [{ x: 1 }, 5],
  ]),
  new Map([
    [{ x: 1 }, 5],
    [{ x: 2 }, 5],
  ])
);

notUtilIsDeepStrict(new Set([3, "3"]), new Set([3, 4]));
notUtilIsDeepStrict(
  new Map([
    [3, 0],
    ["3", 0],
  ]),
  new Map([
    [3, 0],
    [4, 0],
  ])
);

notUtilIsDeepStrict(
  new Set([{ a: 1 }, { a: 1 }, { a: 2 }]),
  new Set([{ a: 1 }, { a: 2 }, { a: 2 }])
);

// Mixed primitive and object keys
utilIsDeepStrict(
  new Map([
    [1, "a"],
    [{}, "a"],
  ]),
  new Map([
    [1, "a"],
    [{}, "a"],
  ])
);
utilIsDeepStrict(new Set([1, "a", [{}, "a"]]), new Set([1, "a", [{}, "a"]]));

// This is an awful case, where a map contains multiple equivalent keys:
notUtilIsDeepStrict(
  new Map([
    [1, "a"],
    ["1", "b"],
  ]),
  new Map([
    ["1", "a"],
    [true, "b"],
  ])
);
notUtilIsDeepStrict(new Set(["a"]), new Set(["b"]));
utilIsDeepStrict(
  new Map([
    [{}, "a"],
    [{}, "b"],
  ]),
  new Map([
    [{}, "b"],
    [{}, "a"],
  ])
);
notUtilIsDeepStrict(
  new Map([
    [true, "a"],
    ["1", "b"],
    [1, "a"],
  ]),
  new Map([
    ["1", "a"],
    [1, "b"],
    [true, "a"],
  ])
);
notUtilIsDeepStrict(
  new Map([
    [true, "a"],
    ["1", "b"],
    [1, "c"],
  ]),
  new Map([
    ["1", "a"],
    [1, "b"],
    [true, "a"],
  ])
);

// Similar object keys
notUtilIsDeepStrict(new Set([{}, {}]), new Set([{}, 1]));
notUtilIsDeepStrict(
  new Set([
    [{}, 1],
    [{}, 1],
  ]),
  new Set([
    [{}, 1],
    [1, 1],
  ])
);
notUtilIsDeepStrict(
  new Map([
    [{}, 1],
    [{}, 1],
  ]),
  new Map([
    [{}, 1],
    [1, 1],
  ])
);
notUtilIsDeepStrict(
  new Map([
    [{}, 1],
    [true, 1],
  ]),
  new Map([
    [{}, 1],
    [1, 1],
  ])
);

// Similar primitive key / values
notUtilIsDeepStrict(new Set([1, true, false]), new Set(["1", 0, "0"]));
notUtilIsDeepStrict(
  new Map([
    [1, 5],
    [true, 5],
    [false, 5],
  ]),
  new Map([
    ["1", 5],
    [0, 5],
    ["0", 5],
  ])
);

// Undefined value in Map
utilIsDeepStrict(new Map([[1, undefined]]), new Map([[1, undefined]]));
notUtilIsDeepStrict(new Map([[1, null]]), new Map([["1", undefined]]));
notUtilIsDeepStrict(new Map([[1, undefined]]), new Map([[2, undefined]]));

// null as key
utilIsDeepStrict(new Map([[null, 3]]), new Map([[null, 3]]));
notUtilIsDeepStrict(new Map([[null, undefined]]), new Map([[undefined, null]]));
notUtilIsDeepStrict(new Set([null]), new Set([undefined]));

// GH-6416. Make sure circular refs don't throw.
{
  const b = {};
  b.b = b;
  const c = {};
  c.b = c;

  utilIsDeepStrict(b, c);

  const d = {};
  d.a = 1;
  d.b = d;
  const e = {};
  e.a = 1;
  e.b = {};

  notUtilIsDeepStrict(d, e);
}

// GH-14441. Circular structures should be consistent
{
  const a = {};
  const b = {};
  a.a = a;
  b.a = {};
  b.a.a = a;
  utilIsDeepStrict(a, b);
}

{
  const a = new Set();
  const b = new Set();
  const c = new Set();
  a.add(a);
  b.add(b);
  c.add(a);
  utilIsDeepStrict(b, c);
}

// GH-7178. Ensure reflexivity of deepEqual with `arguments` objects.
{
  const args = (function () {
    return arguments;
  })();
  notUtilIsDeepStrict([], args);
}

// More checking that arguments objects are handled correctly
{
  // eslint-disable-next-line func-style
  const returnArguments = function () {
    return arguments;
  };

  const someArgs = returnArguments("a");
  const sameArgs = returnArguments("a");
  const diffArgs = returnArguments("b");

  notUtilIsDeepStrict(someArgs, ["a"]);
  notUtilIsDeepStrict(someArgs, { 0: "a" });
  notUtilIsDeepStrict(someArgs, diffArgs);
  utilIsDeepStrict(someArgs, sameArgs);
}

{
  const values = [
    123,
    Infinity,
    0,
    null,
    undefined,
    false,
    true,
    {},
    [],
    () => {},
  ];
  utilIsDeepStrict(new Set(values), new Set(values));
  utilIsDeepStrict(new Set(values), new Set(values.reverse()));

  const mapValues = values.map((v) => [v, { a: 5 }]);
  utilIsDeepStrict(new Map(mapValues), new Map(mapValues));
  utilIsDeepStrict(new Map(mapValues), new Map(mapValues.reverse()));
}

{
  const s1 = new Set();
  const s2 = new Set();
  s1.add(1);
  s1.add(2);
  s2.add(2);
  s2.add(1);
  utilIsDeepStrict(s1, s2);
}

{
  const m1 = new Map();
  const m2 = new Map();
  const obj = { a: 5, b: 6 };
  m1.set(1, obj);
  m1.set(2, "hi");
  m1.set(3, [1, 2, 3]);

  m2.set(2, "hi"); // different order
  m2.set(1, obj);
  m2.set(3, [1, 2, 3]); // Deep equal, but not reference equal.

  utilIsDeepStrict(m1, m2);
}

{
  const m1 = new Map();
  const m2 = new Map();

  // m1 contains itself.
  m1.set(1, m1);
  m2.set(1, new Map());

  notUtilIsDeepStrict(m1, m2);
}

{
  const map1 = new Map([[1, 1]]);
  const map2 = new Map([[1, "1"]]);
  assert.strictEqual(util.isDeepStrictEqual(map1, map2), false);
}

{
  // Two equivalent sets / maps with different key/values applied shouldn't be
  // the same. This is a terrible idea to do in practice, but deepEqual should
  // still check for it.
  const s1 = new Set();
  const s2 = new Set();
  s1.x = 5;
  notUtilIsDeepStrict(s1, s2);

  const m1 = new Map();
  const m2 = new Map();
  m1.x = 5;
  notUtilIsDeepStrict(m1, m2);
}

{
  // Circular references.
  const s1 = new Set();
  s1.add(s1);
  const s2 = new Set();
  s2.add(s2);
  utilIsDeepStrict(s1, s2);

  const m1 = new Map();
  m1.set(2, m1);
  const m2 = new Map();
  m2.set(2, m2);
  utilIsDeepStrict(m1, m2);

  const m3 = new Map();
  m3.set(m3, 2);
  const m4 = new Map();
  m4.set(m4, 2);
  utilIsDeepStrict(m3, m4);
}

// Handle sparse arrays
utilIsDeepStrict([1, , , 3], [1, , , 3]);
notUtilIsDeepStrict([1, , , 3], [1, , , 3, , ,]);

// Handle different error messages
{
  const err1 = new Error("foo1");
  const err2 = new Error("foo2");
  const err3 = new TypeError("foo1");
  notUtilIsDeepStrict(err1, err2, assert.AssertionError);
  notUtilIsDeepStrict(err1, err3, assert.AssertionError);
  notUtilIsDeepStrict(err1, {}, assert.AssertionError);
}

// Handle NaN
assert.strictEqual(util.isDeepStrictEqual(NaN, NaN), true);
assert.strictEqual(util.isDeepStrictEqual({ a: NaN }, { a: NaN }), true);
assert.strictEqual(
  util.isDeepStrictEqual([1, 2, NaN, 4], [1, 2, NaN, 4]),
  true
);

// Handle boxed primitives
{
  const boxedString = new String("test");
  const boxedSymbol = Object(Symbol());
  notUtilIsDeepStrict(new Boolean(true), Object(false));
  notUtilIsDeepStrict(Object(true), new Number(1));
  notUtilIsDeepStrict(new Number(2), new Number(1));
  notUtilIsDeepStrict(boxedSymbol, Object(Symbol()));
  notUtilIsDeepStrict(boxedSymbol, {});
  utilIsDeepStrict(boxedSymbol, boxedSymbol);
  utilIsDeepStrict(Object(true), Object(true));
  utilIsDeepStrict(Object(2), Object(2));
  utilIsDeepStrict(boxedString, Object("test"));
  boxedString.slow = true;
  notUtilIsDeepStrict(boxedString, Object("test"));
  boxedSymbol.slow = true;
  notUtilIsDeepStrict(boxedSymbol, {});
  utilIsDeepStrict(Object(BigInt(1)), Object(BigInt(1)));
  notUtilIsDeepStrict(Object(BigInt(1)), Object(BigInt(2)));

  const booleanish = new Boolean(true);
  Object.defineProperty(booleanish, Symbol.toStringTag, { value: "String" });
  Object.setPrototypeOf(booleanish, String.prototype);
  notUtilIsDeepStrict(booleanish, new String("true"));

  const numberish = new Number(42);
  Object.defineProperty(numberish, Symbol.toStringTag, { value: "String" });
  Object.setPrototypeOf(numberish, String.prototype);
  notUtilIsDeepStrict(numberish, new String("42"));

  const stringish = new String("0");
  Object.defineProperty(stringish, Symbol.toStringTag, { value: "Number" });
  Object.setPrototypeOf(stringish, Number.prototype);
  notUtilIsDeepStrict(stringish, new Number(0));

  const bigintish = new Object(BigInt(42));
  Object.defineProperty(bigintish, Symbol.toStringTag, { value: "String" });
  Object.setPrototypeOf(bigintish, String.prototype);
  notUtilIsDeepStrict(bigintish, new String("42"));

  const symbolish = new Object(Symbol("fhqwhgads"));
  Object.defineProperty(symbolish, Symbol.toStringTag, { value: "String" });
  Object.setPrototypeOf(symbolish, String.prototype);
  notUtilIsDeepStrict(symbolish, new String("fhqwhgads"));
}

// Minus zero
notUtilIsDeepStrict(0, -0);
utilIsDeepStrict(-0, -0);

// Handle symbols (enumerable only)
{
  const symbol1 = Symbol();
  const obj1 = { [symbol1]: 1 };
  const obj2 = { [symbol1]: 1 };
  const obj3 = { [Symbol()]: 1 };
  const obj4 = {};
  // Add a non enumerable symbol as well. It is going to be ignored!
  Object.defineProperty(obj2, Symbol(), { value: 1 });
  Object.defineProperty(obj4, symbol1, { value: 1 });
  notUtilIsDeepStrict(obj1, obj3);
  utilIsDeepStrict(obj1, obj2);
  notUtilIsDeepStrict(obj1, obj4);
  // TypedArrays have a fast path. Test for this as well.
  const a = new Uint8Array(4);
  const b = new Uint8Array(4);
  a[symbol1] = true;
  b[symbol1] = false;
  notUtilIsDeepStrict(a, b);
  b[symbol1] = true;
  utilIsDeepStrict(a, b);
  // The same as TypedArrays is valid for boxed primitives
  const boxedStringA = new String("test");
  const boxedStringB = new String("test");
  boxedStringA[symbol1] = true;
  notUtilIsDeepStrict(boxedStringA, boxedStringB);
  boxedStringA[symbol1] = true;
  utilIsDeepStrict(a, b);
}