import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
import { join } from "path";

const exitCode0 = [
  " ",
  "\n",
  "\t",
  "\r\n",
  "    ",
  "\n\n\n",
  "export default '\\u{000C}'",
  "export default '\\u{200B}'",
  "export default '\\u{200C}'",
  "export default '\\u{200D}'",
  "export default '\\u{FEFF}'",
  '"use strict";',
  '"use strict";\n',
  "'use strict';",
  '"use strict";\n\n',
  '"use asm";',
  '"use asm";\n',
  "'use strict'; 'use asm';",
  '"use strict"; "use strict";',
  "// empty comment",
  "/* empty block comment */",
  "/** JSDoc comment */",
  "//\n//\n//",
  "/* \n    Multi\n    line\n    comment\n  */",
  "-->",
  "// TODO: ",
  "/** @type {number} */",
  "const a = 123;",
  "let a;",
  "var a = undefined;",
  "const a = null;",
  "const [a] = [];",
  "const {a} = {};",
  "const [...a] = [];",
  "const {...a} = {};",
  "let [a = 1] = [];",
  "let {a: b = 1} = {};",
  "``",
  "`template`",
  "`template${123}`",
  "`${null}`",
  "`${'`'}`",
  "`\n`",
  "`\n`",
  "({});",
  "({ a: 1 });",
  "({ ['computed']: 1 });",
  "({ get x() { return 1; } });",
  "({ __proto__: null });",
  "({ get [1+2](){}, set [1+2](x){} });",
  "({ a: 1, ...{b: 2} });",
  "[];",
  "[,];",
  "[,,];",
  "[1,];",
  "[,...[1]];",
  "[,,...[1]];",
  "[[[[[]]]]]",
  "[...[...[...[]]]]",
  "[1,,2,,3];",
  "Array(1_000_000).fill(1);",
  "()=>{};",
  "async()=>{};",
  "(function(){}).bind(null);",
  "()=>()=>()=>1;",
  "function f(a=1,{b}={},...c){};",
  "async function* f(){await 1; yield 2;};",
  "(async()=>await 1)();",
  "class A{}",
  "class A extends null{}",
  "class A{#private=1}",
  "class A{static{}}",
  "class A{get #a(){} set #a(x){}}",
  "class A{*#gen(){yield this.#p}#p=1}",
  "class A extends class B{}{};",
  "export {};",
  "export default 1;",
  "export default function(){};",
  "import.meta.url;",
  "const pi = Math.PI;",
  "const hello = 'world';",
  "const star = '⭐';",
  "'\\u0000\\uFFFF';",
  "'\\x00\\xFF';",
  "const \\u{61} = 1;",
  "'\\0';",
  "'\\v\\f\\b';",
  "'\\\r\n';",
  "/./;",
  "/[]/;",
  "/[^]/;",
  "/\\u{0}/u;",
  "/[\\u0000-\\u{10FFFF}]/u;",
  "/./gimsuyd;",
  "/\\p{Script=Latin}/u;",
  "/(?<=a)b/;",
  "/(?<!a)b/;",
  "/\\k<name>/;",
  "1n;",
  "0n;",
  "9007199254740991n;",
  "0b1n;",
  "0o7n;",
  "0xFn;",
  "1_2_3n;",
  "BigInt(1);",
  "-0n;",
  "~0n;",
  "0b1_0_1;",
  "0o7_7_7;",
  "0xF_F_F;",
  "1_2_3_4;",
  "try{}catch{}",
  "try{}catch(e){}",
  "try{}catch{}finally{}",
  "try{}finally{}",
  "try{throw 1}catch(e){}",
  "try{throw new Error}catch{}",
  "try{throw{}}catch({message}){}",
  "try{throw null}catch(e){}",
  "try{throw undefined}catch(e){}",
  "try{throw function(){}}catch(e){}",
  "function*g(){yield;}",
  "function*g(){yield*g();}",
  "async function*g(){yield;}",
  "async function*g(){await 1;}",
  "(async function*(){for await(x of y){}});",
  "function*g(){try{yield}finally{}}",
  "[...new Set];",
  "for(x of[]){}",
  "for await(x of[]){}",
  "async function f(){for await(x of y){}}",
  "void 0;",
  "new (class{})();",
  "(class extends null{});",
  "(class{[Symbol.hasInstance](){}});",
  "function*g(){yield yield yield;}",
  "1..toString();",
  "`${`${`${1}`}`}`",
  "String.raw`\n`",
  "String.raw`\\n`",
  "`${`${function*(){yield 1}}`}`",
  "`${class{}}${()=>{}}${/./}`",
  "`${`${async()=>await 1}`}`",
  "`${`${class{static{``}}}`}`",
  "await import('bun');",
  "await import('bun:ffi');",
  "await import(import.meta.path);",
  "/(?:)/",
  "/\\b\\B\\d\\D\\w\\W\\s\\S/",
  "/\\cA\\cZ\\ca\\cz/",
  "/\\p{General_Category=Letter}/u",
  "/\\p{Script_Extensions=Latin}/u",
  "/(?<=(?=a)b)c/",
  "/(?<!(?!a)b)c/",
  "/\\k<name>\\k<other>/",
  "/\\u{10FFFF}\\u{0000}/u",
  "/[\\p{ASCII}--\\p{Decimal_Number}]/v",
  "const [{a: [b = 1] = []} = [{a: [2]}]] = [];",
  "let {a: {b: {c = 1} = {}} = {}} = {};",
  "const [a, , ...{0: b, length}] = [];",
  "const {[class{}.prop]: x} = {};",
  "const {[()=>{}]: x} = {};",
  "let {[/./]: x} = {};",
  "const {[class extends null{}]: x} = {};",
  "const {[function*(){}]: x} = {};",
  "function f([a] = [1], {b} = {b: 2}, ...c){}",
  "(function({[key]: value} = {}){})",
  "(({[this?.prop]: x} = {})=>{})",
  "function f(a = () => b, b = 1){}",
  "(a = class{}, b = new a)=>{}",
  "function f(a = new.target){}",
  "for await(const x of async function*(){yield 1}()){}",
  "for(const x of function*(){yield 1}()){}",
  "for(const {[key]: x} of []){}",
  "for(let [{a = 1} = {}] of []){}",
  "do{continue}while(false);",
  "label:do{break label}while(false);",
  "outer:for(;;)inner:break outer;",
  "block:{break block;}",
  "while(false)with(null);",
  "switch(0){case 1:default:case 2:}",
  "try{throw async()=>{}}catch(e){await e}",
  "try{throw class{}}catch(e){new e}",
  "try{throw{[Symbol.iterator]:()=>{}}}catch(e){}",
  "try{throw Object.create(null)}catch(e){}",
  "try{throw new Proxy({},{})}catch(e){}",
  "try{throw new WeakMap}catch(e){}",
  "try{throw new Int32Array}catch(e){}",
  "try{throw new SharedArrayBuffer(0)}catch(e){}",
  "try{throw new WebAssembly.Module(new Uint8Array)}catch(e){}",
  "void void void 0;",
  "typeof typeof typeof 0;",
  "delete (delete (delete 0));",
  "await (await (await 1));",
  "--{a:1}.a;",
  "(class{}).prototype.constructor;",
  "(()=>{}).prototype?.call;",
  "(async()=>{}).prototype?.call;",
  "(function*(){}).prototype.next;",
  "(async function*(){}).prototype.next;",
  "this?.prop?.[key]?.();",
  "0b0_1_0_1;",
  "0B0_1_0_1;",
  "0o0_1_2_3;",
  "0O0_1_2_3;",
  "0x0_1_2_3;",
  "0X0_1_2_3;",
  "1_2_3.4_5_6;",
  "1_2_3e1_2_3;",
  "1_2_3E1_2_3;",
  ".0_1_2_3;",
  "0b1_0_1n;",
  "0B1_0_1n;",
  "0o1_2_3n;",
  "0O1_2_3n;",
  "0x1_2_3n;",
  "0X1_2_3n;",
  "1_2_3_4_5n;",
  "BigInt(0b101);",
  "BigInt(0o777);",
  "BigInt(0xff);",
  "for(const [,,...a] of [[]]){}",
  "for(let {a: [b]} of []){}",
  "for await(const [a = await 1] of []){}",
  "for(const {[await 1]: x} of []){}",
  "for(var {[class{}]: x} of []){}",
  "for(let {[/./]: x} of []){}",
  "for(const {[`${a}`]: x} of []){}",
  "for await(const x of async function*(){yield*[1,2,3]}()){}",
  "new class extends (await Promise.resolve(class{})){}",
  "function*g(){yield yield*function*(){yield yield yield;}();}",
  "async function*f(){yield await Promise.all([1,2,3])}",
  "async function*f(){yield*[await 1,await 2]}",
  "(async function(){await (async()=>1)()})();",
  "async function*f(){for await(const x of async function*(){yield 1}())yield x}",
  "async function f(){return await new Promise(r=>r(1))}",
  "async function*f(){try{yield await 1}finally{await 2}}",
  "(async()=>{for await(const x of[])for await(const y of[]){}})();",
  "async function*f(){yield await(yield await(yield await 1))}",
  "async function*f(){yield*async function*(){yield await 1}()}",
  "export default await(async()=>1)();",
  "function*g(){yield*{[Symbol.iterator]:function*(){yield 1}}}",
  "function*g(){yield*{async*[Symbol.asyncIterator](){yield 1}}}",
  "function*g(){yield*function*(){yield function*(){yield 1}}()}",
  "function*g(){yield*{get[Symbol.iterator](){return function*(){yield 1}}}}",
  "function*g(){yield*(class{static*[Symbol.iterator](){yield 1}});}",
  "function*g(){yield*class{static*[Symbol.iterator](){yield 1}}}",
  "function*g(){yield*{*[Symbol.iterator](){yield class{}}}}",
  "function*g(){yield*{*[Symbol.iterator](){yield function(){}}}}",
  "function*g(){yield*{*[Symbol.iterator](){yield async()=>{}}}}",
  "function*g(){yield*{*[Symbol.iterator](){yield function*(){}}}}",
  "({[function*(){yield 1}]:1});",
  "({[async function*(){yield 1}]:1});",
  "({[(()=>class{})()]:1});",
  "({[new class{valueOf(){return 1}}]:2});",
  "({[new class{[Symbol.toPrimitive](){return 1}}]:2});",
  "({[new class{toString(){return '1'}}]:2});",
  "({[new class{get [Symbol.toPrimitive](){return()=>1}}]:2});",
  "({[new class{static{this.prototype.valueOf=()=>1}}]:2});",
  "label:while(1)try{continue label}finally{break label}",
  "new Proxy(class{},{construct(){return{}}});",
  "new Proxy(function(){},{apply(){return{}}});",
  "new Proxy({},{get(){return new Proxy({},{})}})",
  "new Proxy([],{get(){return new Proxy({},{})}});",
  "new Proxy(function(){},{construct(){return new Proxy({},{})}})",
  "new Proxy(class{},{construct(){return new Proxy({},{})}})",
  "new Proxy({},{get(){return function(){return new Proxy({},{})}}})",
  "new Proxy(function(){},{apply(){return new Proxy(class{},{})}})",
  "new Proxy(class{},{construct(){return new Proxy(function(){},{})}})",
  "Reflect.get(new Proxy({},{}),'')",
  "Reflect.set(new Proxy({},{}),'')",
  "Reflect.has(new Proxy({},{}),'')",
  "Reflect.deleteProperty(new Proxy({},{}),'')",
  "Reflect.getOwnPropertyDescriptor(new Proxy({},{}),'')",
  "Reflect.getPrototypeOf(new Proxy({},{}))",
  "Reflect.setPrototypeOf(new Proxy({},{}),{})",
  "Reflect.isExtensible(new Proxy({},{}))",
  "Reflect.preventExtensions(new Proxy({},{}))",
  "Promise.all([Promise.race([]),Promise.allSettled([])])",
  "Promise.race([Promise.all([]),Promise.any([])])",
  "Promise.allSettled([Promise.race([]),Promise.all([])])",
  "Promise.any([Promise.allSettled([]),Promise.race([])])",
  "Promise.resolve(Promise.reject().catch(()=>Promise.all([])))",
  "new Function(`return function*(){${`yield${`yield${`yield`}`}`}}`)();",
  "new Function(`return async()=>${`await${`await${`1`}`}`}`)();",
  "new Function(`return class{${`*[Symbol.iterator](){${`yield 1`}}`}}`)();",
  "new Function(`return class{${`async*[Symbol.asyncIterator](){${`yield 1`}}`}}`)();",
  "new Function(`return class{${`get #a(){${`return 1`}}`}}`)();",
  "new Function(`return class{${`set #a(v){${`this.#b=v`}}#b`}}`)();",
  "new Function(`return class{${`#a;${`#b;${`#c`}`}`}}`)();",
  "for await(let {...x} of async function*(){yield*[]}()){}",
  "for await(const x of (async function*(){yield await 1})()){}",
  "function f(){let a=b,b=1}",
  "(function(){const[a=b,b=1]=[]})",
  "(()=>{let{a=b,b=1}={}})",
];

describe("exit code 0", () => {
  const fixturePath = tempDirWithFiles("fixture", {
    [`package.json`]: `{
      "name": "test",

    }`,
    "fixture.js": "export default 1",
  });

  for (let i = 0; i < exitCode0.length; i++) {
    const source = exitCode0[i];

    test(`file #${i}: ${JSON.stringify(source)}`, async () => {
      await Bun.write(join(fixturePath, "fixture.js"), source);
      await using proc = Bun.spawn([bunExe(), "./fixture.js"], {
        stdout: "inherit",
        env: bunEnv,
        cwd: fixturePath,
        stderr: "inherit",
        stdin: "inherit",
      });
      const exitCode = await proc.exited;
      expect(exitCode).toBe(0);
    });

    test(`eval #${i}: ${JSON.stringify(source)}`, async () => {
      await using proc = Bun.spawn([bunExe(), "--eval", source], {
        stdout: "inherit",
        env: bunEnv,
        stderr: "inherit",
        stdin: "inherit",
      });
      const exitCode = await proc.exited;
      expect(exitCode).toBe(0);
    });
  }
});
