/*
 * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
 * - test/built-ins/Array/prototype/map/15.4.4.19-8-1.js
 * - test/built-ins/Array/prototype/map/15.4.4.19-8-2.js
 * - test/built-ins/Array/prototype/map/15.4.4.19-8-b-1.js
 * - test/built-ins/Array/prototype/filter/15.4.4.20-9-1.js
 * - test/built-ins/Array/prototype/filter/15.4.4.20-9-2.js
 * - test/built-ins/Array/prototype/filter/15.4.4.20-9-b-1.js
 * - test/built-ins/Array/prototype/find/predicate-call-parameters.js
 * - test/built-ins/Array/prototype/find/predicate-not-called-on-empty-array.js
 * - test/built-ins/Array/prototype/find/return-found-value-predicate-result-is-true.js
 * - test/built-ins/Array/prototype/find/return-undefined-if-predicate-returns-false-value.js
 * - test/built-ins/Array/prototype/findIndex/predicate-call-parameters.js
 * - test/built-ins/Array/prototype/findIndex/return-index-predicate-result-is-true.js
 * - test/built-ins/Array/prototype/findIndex/return-negative-one-if-predicate-returns-false-value.js
 * - test/built-ins/Array/prototype/findLast/predicate-call-parameters.js
 * - test/built-ins/Array/prototype/findLast/return-found-value-predicate-result-is-true.js
 * - test/built-ins/Array/prototype/findLast/return-undefined-if-predicate-returns-false-value.js
 * - test/built-ins/Array/prototype/findLastIndex/predicate-call-parameters.js
 * - test/built-ins/Array/prototype/findLastIndex/return-index-predicate-result-is-true.js
 * - test/built-ins/Array/prototype/findLastIndex/return-negative-one-if-predicate-returns-false-value.js
 * - test/built-ins/Array/prototype/some/15.4.4.17-7-1.js
 * - test/built-ins/Array/prototype/some/15.4.4.17-8-1.js
 * - test/built-ins/Array/prototype/every/15.4.4.16-7-1.js
 * - test/built-ins/Array/prototype/every/15.4.4.16-8-1.js
 * - test/built-ins/Array/prototype/forEach/15.4.4.18-7-1.js
 * - test/built-ins/Array/prototype/forEach/15.4.4.18-7-2.js
 * - test/built-ins/Array/prototype/reduce/15.4.4.21-9-5.js
 * - test/built-ins/Array/prototype/reduce/15.4.4.21-9-c-ii-20.js
 * - test/built-ins/Array/prototype/reduce/15.4.4.21-9-1.js
 * - test/built-ins/Array/prototype/reduce/15.4.4.21-10-1.js
 * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-5.js
 * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-c-ii-20.js
 * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-1.js
 * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-10-1.js
 * - test/built-ins/Array/prototype/flatMap/depth-always-one.js
 * - test/built-ins/Array/prototype/flatMap/mapperfunction-throws.js
 * - test/built-ins/Array/prototype/sort/S15.4.4.11_A1.1_T1.js
 * - test/built-ins/Array/prototype/sort/S15.4.4.11_A2.1_T1.js
 * - test/built-ins/Array/prototype/sort/stability-5-elements.js
 * - test/built-ins/Array/prototype/toSorted/comparefn-controls-sort.js
 * - test/built-ins/Array/prototype/toSorted/comparefn-default.js
 * - test/built-ins/Array/prototype/toSorted/immutable.js
 * - test/built-ins/Array/prototype/toSorted/zero-or-one-element.js
 *
 * Copyright (C) 2015 the V8 project authors. All rights reserved.
 * Copyright (C) 2018 Mathias Bynens. All rights reserved.
 * Copyright (C) 2018 Shilpi Jain and Michael Ficarra. All rights reserved.
 * Copyright (C) 2021 Igalia, S.L. All rights reserved.
 * Copyright (C) 2021 Microsoft. All rights reserved.
 * Copyright (C) 2025 Google. All rights reserved.
 * Copyright (C) 2026 Garham Lee. All rights reserved.
 * Copyright (c) 2012 Ecma International.  All rights reserved.
 * Copyright 2009 the Sputnik authors.  All rights reserved.
 * Test262 portions are governed by the BSD license in LICENSE.test262.
 */
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "../src/index.js"

const value = async (code: string) => {
  const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
  if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
  return result.value
}

const cases = [
  {
    path: "test/built-ins/Array/prototype/map/15.4.4.19-8-1.js",
    code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const result = input.map((value) => { input[2] = 3; input[5] = 6; return 1 }); return [result.length, result[5] === undefined]`,
    expected: [5, true],
  },
  {
    path: "test/built-ins/Array/prototype/map/15.4.4.19-8-2.js",
    code: `const input = [1, 2, 3, 4, 5]; const result = input.map((value) => { input[4] = -1; return value > 0 ? 1 : 0 }); return [result.length, result[4]]`,
    expected: [5, 0],
  },
  {
    path: "test/built-ins/Array/prototype/map/15.4.4.19-8-b-1.js",
    code: `const input = []; input[10] = 0; input.pop(); input[1] = undefined; let calls = 0; const result = input.map(() => { calls += 1; return 1 }); return [result.length, calls, 0 in result, 1 in result]`,
    expected: [10, 1, false, true],
  },
  {
    path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-1.js",
    code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const result = input.filter(() => { input[2] = 3; input[5] = 6; return true }); return result`,
    expected: [1, 2, 3, 4, 5],
  },
  {
    path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-2.js",
    code: `const input = [1, 2, 3, 4, 5]; return input.filter((value) => { input[2] = -1; input[4] = -1; return value > 0 })`,
    expected: [1, 2, 4],
  },
  {
    path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-b-1.js",
    code: `const input = []; input[9] = 0; input.pop(); input[1] = undefined; let calls = 0; const result = input.filter(() => { calls += 1; return false }); return [result, calls]`,
    expected: [[], 1],
  },
  {
    path: "test/built-ins/Array/prototype/find/predicate-call-parameters.js",
    code: `const input = [10, 20]; const seen = []; input.find((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
    expected: [
      [10, 0, true],
      [20, 1, true],
    ],
  },
  {
    path: "test/built-ins/Array/prototype/find/return-found-value-predicate-result-is-true.js",
    code: `return [1, 2, 3].find((value) => value > 1)`,
    expected: 2,
  },
  {
    path: "test/built-ins/Array/prototype/find/return-undefined-if-predicate-returns-false-value.js",
    code: `return [1, 2, 3].find((value) => value > 4) === undefined`,
    expected: true,
  },
  {
    path: "test/built-ins/Array/prototype/find/predicate-not-called-on-empty-array.js",
    code: `let calls = 0; const result = [].find(() => { calls += 1; return true }); return [result === undefined, calls]`,
    expected: [true, 0],
  },
  {
    path: "test/built-ins/Array/prototype/findIndex/predicate-call-parameters.js",
    code: `const input = [10, 20]; const seen = []; input.findIndex((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
    expected: [
      [10, 0, true],
      [20, 1, true],
    ],
  },
  {
    path: "test/built-ins/Array/prototype/findIndex/return-index-predicate-result-is-true.js",
    code: `return [1, 2, 3].findIndex((value) => value > 1)`,
    expected: 1,
  },
  {
    path: "test/built-ins/Array/prototype/findIndex/return-negative-one-if-predicate-returns-false-value.js",
    code: `return [1, 2, 3].findIndex((value) => value > 4)`,
    expected: -1,
  },
  {
    path: "test/built-ins/Array/prototype/findLast/predicate-call-parameters.js",
    code: `const input = [10, 20]; const seen = []; input.findLast((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
    expected: [
      [20, 1, true],
      [10, 0, true],
    ],
  },
  {
    path: "test/built-ins/Array/prototype/findLast/return-found-value-predicate-result-is-true.js",
    code: `return [1, 2, 3].findLast((value) => value < 3)`,
    expected: 2,
  },
  {
    path: "test/built-ins/Array/prototype/findLast/return-undefined-if-predicate-returns-false-value.js",
    code: `return [1, 2, 3].findLast((value) => value > 4) === undefined`,
    expected: true,
  },
  {
    path: "test/built-ins/Array/prototype/findLastIndex/predicate-call-parameters.js",
    code: `const input = [10, 20]; const seen = []; input.findLastIndex((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`,
    expected: [
      [20, 1, true],
      [10, 0, true],
    ],
  },
  {
    path: "test/built-ins/Array/prototype/findLastIndex/return-index-predicate-result-is-true.js",
    code: `return [1, 2, 3].findLastIndex((value) => value < 3)`,
    expected: 1,
  },
  {
    path: "test/built-ins/Array/prototype/findLastIndex/return-negative-one-if-predicate-returns-false-value.js",
    code: `return [1, 2, 3].findLastIndex((value) => value > 4)`,
    expected: -1,
  },
  {
    path: "test/built-ins/Array/prototype/some/15.4.4.17-7-1.js",
    code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const seen = []; const result = input.some((value) => { input[2] = 3; seen.push(value); return false }); return [result, seen.includes(3)]`,
    expected: [false, true],
  },
  {
    path: "test/built-ins/Array/prototype/some/15.4.4.17-8-1.js",
    code: `return [].some(() => true)`,
    expected: false,
  },
  {
    path: "test/built-ins/Array/prototype/every/15.4.4.16-7-1.js",
    code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const seen = []; const result = input.every((value) => { input[2] = 3; seen.push(value); return true }); return [result, seen.includes(3)]`,
    expected: [true, true],
  },
  {
    path: "test/built-ins/Array/prototype/every/15.4.4.16-8-1.js",
    code: `return [].every(() => false)`,
    expected: true,
  },
  {
    path: "test/built-ins/Array/prototype/forEach/15.4.4.18-7-1.js",
    code: `const input = [1, 2]; input[3] = 4; input[4] = 5; let calls = 0; input.forEach(() => { calls += 1; input[2] = 3; input[5] = 6 }); return calls`,
    expected: 5,
  },
  {
    path: "test/built-ins/Array/prototype/forEach/15.4.4.18-7-2.js",
    code: `const input = [1, 2, 3]; const seen = []; input.forEach((value, index) => { seen.push(value); if (index === 0) input.pop() }); return seen`,
    expected: [1, 2],
  },
  {
    path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-1.js",
    code: `const input = [1, 2]; input[3] = 4; input[4] = "5"; return input.reduce((accumulator, value) => { input[2] = 3; input[5] = 6; return accumulator + value })`,
    expected: "105",
  },
  {
    path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-5.js",
    code: `let calls = 0; const result = [1].reduce(() => { calls += 1; return 2 }); return [result, calls]`,
    expected: [1, 0],
  },
  {
    path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-c-ii-20.js",
    code: `let accessed = false; const result = [11].reduce((previous) => { accessed = true; return previous === undefined }, undefined); return [result, accessed]`,
    expected: [true, true],
  },
  {
    path: "test/built-ins/Array/prototype/reduce/15.4.4.21-10-1.js",
    code: `const input = [1, 2, 3, 4, 5]; input.reduce(() => 1); return input`,
    expected: [1, 2, 3, 4, 5],
  },
  {
    path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-1.js",
    code: `const input = ["1", 2]; input[3] = 4; input[4] = "5"; return input.reduceRight((accumulator, value) => { input[2] = 3; input[5] = 6; return accumulator + value })`,
    expected: "54321",
  },
  {
    path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-5.js",
    code: `let calls = 0; const result = [1].reduceRight(() => { calls += 1; return 2 }); return [result, calls]`,
    expected: [1, 0],
  },
  {
    path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-c-ii-20.js",
    code: `let accessed = false; const result = [11].reduceRight((previous) => { accessed = true; return previous === undefined }, undefined); return [result, accessed]`,
    expected: [true, true],
  },
  {
    path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-10-1.js",
    code: `const input = [1, 2, 3, 4, 5]; input.reduceRight(() => 1); return input`,
    expected: [1, 2, 3, 4, 5],
  },
  {
    path: "test/built-ins/Array/prototype/flatMap/depth-always-one.js",
    code: `return [1, 2, 3].flatMap((value) => [[value * 2]])`,
    expected: [[2], [4], [6]],
  },
  {
    path: "test/built-ins/Array/prototype/flatMap/mapperfunction-throws.js",
    code: `try { [1, 2].flatMap(() => { throw "stop" }) } catch (error) { return error === "stop" } return false`,
    expected: true,
  },
  {
    path: "test/built-ins/Array/prototype/sort/S15.4.4.11_A1.1_T1.js",
    code: `const input = []; input[2] = 0; input.pop(); input.sort(); return [input.length, input[0] === undefined, input[1] === undefined]`,
    expected: [2, true, true],
  },
  {
    path: "test/built-ins/Array/prototype/sort/S15.4.4.11_A2.1_T1.js",
    code: `return ["z", "y", "x", "w", "v", "u", "t", "s", "r", "q", "p", "o", "n", "M", "L", "K", "J", "I", "H", "G", "F", "E", "D", "C", "B", "A"].sort()`,
    expected: [
      "A",
      "B",
      "C",
      "D",
      "E",
      "F",
      "G",
      "H",
      "I",
      "J",
      "K",
      "L",
      "M",
      "n",
      "o",
      "p",
      "q",
      "r",
      "s",
      "t",
      "u",
      "v",
      "w",
      "x",
      "y",
      "z",
    ],
  },
  {
    path: "test/built-ins/Array/prototype/sort/stability-5-elements.js",
    code: `const input = [{ n: "A", r: 2 }, { n: "B", r: 3 }, { n: "C", r: 2 }, { n: "D", r: 3 }, { n: "E", r: 3 }]; return input.sort((left, right) => right.r - left.r).map((item) => item.n).join("")`,
    expected: "BDEAC",
  },
  {
    path: "test/built-ins/Array/prototype/toSorted/comparefn-controls-sort.js",
    code: `const mixed = [333, 33, 3, 222, 22, 2, 111, 11, 1]; return [[1, 2, 3, 4].toSorted((a, b) => a - b), [4, 3, 2, 1].toSorted((a, b) => a - b), mixed.toSorted((a, b) => a - b), [1, 2, 3, 4].toSorted((a, b) => b - a), [4, 3, 2, 1].toSorted((a, b) => b - a), mixed.toSorted((a, b) => b - a)]`,
    expected: [
      [1, 2, 3, 4],
      [1, 2, 3, 4],
      [1, 2, 3, 11, 22, 33, 111, 222, 333],
      [4, 3, 2, 1],
      [4, 3, 2, 1],
      [333, 222, 111, 33, 22, 11, 3, 2, 1],
    ],
  },
  {
    path: "test/built-ins/Array/prototype/toSorted/comparefn-default.js",
    code: `return [[1, 2, 3, 4].toSorted(), [4, 3, 2, 1].toSorted(), ["a", 2, 1, "z"].toSorted(), [333, 33, 3, 222, 22, 2, 111, 11, 1].toSorted()]`,
    expected: [
      [1, 2, 3, 4],
      [1, 2, 3, 4],
      [1, 2, "a", "z"],
      [1, 11, 111, 2, 22, 222, 3, 33, 333],
    ],
  },
  {
    path: "test/built-ins/Array/prototype/toSorted/immutable.js",
    code: `const input = [2, 0, 1]; const result = input.toSorted(); return [input, result !== input]`,
    expected: [[2, 0, 1], true],
  },
  {
    path: "test/built-ins/Array/prototype/toSorted/zero-or-one-element.js",
    code: `const zero = []; const one = [1]; const zeroResult = zero.toSorted(); const oneResult = one.toSorted(); return [zeroResult, oneResult, zeroResult !== zero, oneResult !== one]`,
    expected: [[], [1], true, true],
  },
] as const

describe("Test262 Array callback adaptations", () => {
  for (const item of cases) {
    test(item.path, async () => {
      expect(await value(item.code)).toEqual(item.expected)
    })
  }
})

describe("Array callback regressions", () => {
  test("reduce and reduceRight find the first present element", async () => {
    expect(
      await value(`
        const left = []
        left[2] = 3
        const right = []
        right[0] = 4
        right[3] = 1
        right.pop()
        return [left.reduce((a, b) => a + b), right.reduceRight((a, b) => a + b)]
      `),
    ).toEqual([3, 4])
  })

  test("reduce and reduceRight reject arrays containing only holes", async () => {
    expect(
      await value(`
        const values = []
        values[2] = 1
        values.pop()
        let left
        let right
        try { values.reduce((a, b) => a + b) } catch (error) { left = error.name }
        try { values.reduceRight((a, b) => a + b) } catch (error) { right = error.name }
        return [left, right]
      `),
    ).toEqual(["TypeError", "TypeError"])
  })

  test("findLast returns the value observed before predicate mutation", async () => {
    expect(
      await value(`
        const values = [1]
        return values.findLast((item, index, array) => {
          array[index] = 2
          return true
        })
      `),
    ).toBe(1)
  })
})
