import { describe } from "bun:test";
import { dedent, itBundled } from "./expectBundled";

interface TemplateStringTest {
  expr: string;
  print?: string | boolean; // expect stdout
  capture?: string | boolean; // expect literal transpilation
  captureRaw?: string; // expect raw transpilation
}

const templateStringTests: Record<string, TemplateStringTest> = {
  // note for writing tests: .print is .trim()'ed due to how run.stdout works
  Empty: { expr: '""', captureRaw: '""' },
  NullByte: { expr: '"hello\0"', captureRaw: '"hello\\x00"' },
  EmptyTemplate: { expr: "``", captureRaw: '""' },
  ConstantTemplate: { expr: "`asdf`", captureRaw: '"asdf"' },
  AddConstant: { expr: "`${7 + 6}`", capture: true },
  AddConstant2: { expr: "`${7 + 6 + 96}`", capture: true },
  AddConstant3: { expr: "`${0.1 + 0.2}`", print: true },
  SubtractConstant: { expr: "`${7 - 6}`", capture: true },
  SubtractConstant2: { expr: "`${7 - 6 - 10}`", capture: true },
  MultiplyConstant: { expr: "`${7 * 6}`", capture: true },
  MultiplyConstant2: { expr: "`${7 * 6 * 2}`", capture: true },
  MultiplyConstant3: { expr: "`${7.5 * 6.02}`", print: true },
  DivideConstant: { expr: "`${7 / 6}`", print: true },
  DivideConstant2: { expr: "`${7 / 6 / 2}`", print: true },
  DivideConstant3: { expr: "`${7.5 / 6.02}`", print: true },
  Exponent1: { expr: "`${1e0}`", capture: true },
  Exponent2: { expr: "`${1e1}`", capture: true },
  Exponent3: { expr: "`${0e1337}`", capture: true },
  Exponent4: { expr: "`${-1e0}`", capture: true },
  BigExponent1: { expr: "`${1e20}`", print: "100000000000000000000" },
  BigExponent2: { expr: "`${1e21}`", print: "1e+21" },
  BigNumber1: { expr: "`${999999999999999934463.9999999}`", print: "999999999999999900000" },
  BigNumber2: { expr: "`${999999999999999934464.0000000}`", print: "1e+21" },
  True: { expr: "`${true}`", capture: true },
  False: { expr: "`${false}`", capture: true },
  BigInt: { expr: "`${1n}`", print: "1" },
  LongBigInt: {
    expr: "`${-" + "1234".repeat(1000) + "n}`",
    print: "-" + "1234".repeat(1000),
  },
  BigIntAdd: { expr: "`${1n + 2n}`", print: "3" },
  BigIntSubtract: { expr: "`${1n - 2n}`", print: "-1" },
  BigIntMultiply: { expr: "`${2n * 3n}`", print: "6" },
  BigIntDivide: { expr: "`${6n / 2n}`", print: "3" },
  BigIntModulo: { expr: "`${6n % 4n}`", print: "2" },
  BigIntExponent: { expr: "`${2n ** 3n}`", print: "8" },
  ArrowFunction: { expr: "`${() => 123}`", captureRaw: "`${()=>123}`" },
  Function: { expr: "`${function() { return 123; }}`", captureRaw: "`${function(){return 123}}`" },
  Identifier: { expr: "`${ident}`", captureRaw: "`${ident}`" },
  IdentifierAdd: { expr: "`${ident + ident}`", captureRaw: "`${ident+ident}`" },
  IdentifierConstAdd: { expr: "`${2 + ident}`", captureRaw: "`${2+ident}`" },
  EscapeIssue1: {
    expr: `\`\\abc\${ident}\``,
    captureRaw: `\`abc\${ident}\``,
  },
  EscapeIssue2: {
    expr: `\`\\abc\${ident}\``,
    captureRaw: `\`abc\${ident}\``,
  },
  TernaryWithEscapeVariable: {
    expr: '`${"1"}\\${${VARIABLE ? "SOMETHING" : ""}`',
    captureRaw: '`1\\${${VARIABLE?"SOMETHING":""}`',
  },
  TernaryWithEscapeTrue: {
    expr: '`${"1"}\\${${true ? "SOMETHING" : ""}`',
    captureRaw: '"1${SOMETHING"',
  },
  TernaryWithEscapeFalse: {
    expr: '`${"1"}\\${${false ? "SOMETHING" : ""}`',
    captureRaw: '"1${"',
  },
  Fold: { expr: "`a${'b'}c${'d'}e`", capture: true },
  FoldNested1: { expr: "`a${`b`}c${`${'d'}`}e`", capture: true },
  FoldNested2: { expr: "`a${`b`}c${`1${'d'}`}e`", capture: true },
  FoldNested3: { expr: "`a${`b`}c${`${'1'}${'d'}`}e`", capture: true },
  FoldNested4: { expr: "`a${`b`}c${`${`${`${'d'}`}`}`}e`", capture: true },
  FoldNested5: { expr: "`\\$${`d`}`", print: true }, // could be captured
  FoldNested6: { expr: "`a\0${5}c\\${{$${`d`}e`", print: true },
  EscapedDollar: { expr: "`\\${'a'}`", captureRaw: "\"${'a'}\"" },
  EscapedDollar2: { expr: "`\\${'a'}\\${'b'}`", captureRaw: "\"${'a'}${'b'}\"" },
  StringAddition: { expr: "`${1}\u2796` + 'rest'", print: true },
  StringAddition2: { expr: "`\u2796${1}` + `a${Number(1)}b`", print: true },
  StringAddition3: { expr: '`0${"\u2796"}` + `a${Number(1)}b`', print: true },
  StringAddition4: { expr: "`${1}z` + `\u2796${Number(1)}rest`", print: true },
  StringAddition5: { expr: "`\u2796${1}z` + `\u2796${Number(1)}rest`", print: true },
  StringAddition6: { expr: "`${1}` + '\u2796rest'", print: true },
};

describe("bundler", () => {
  for (const key in templateStringTests) {
    const test = templateStringTests[key];
    if ([test.capture, test.captureRaw, test.print].filter(x => x !== undefined).length !== 1) {
      throw new Error(`Exactly one of capture or print must be defined for 'template/${key}'`);
    }
    let captureRaw = test.captureRaw;
    if (test.capture === true) captureRaw = JSON.stringify(eval(test.expr)) as string;
    else if (test.capture !== undefined) captureRaw = JSON.stringify(test.capture);
    if (test.print === true) test.print = eval(test.expr) as string;

    itBundled(
      `string/${key}`,
      captureRaw !== undefined
        ? {
            files: {
              "index.ts": dedent`
                capture(${test.expr});
              `,
            },
            capture: [captureRaw],
            minifySyntax: true,
            minifyWhitespace: true,
          }
        : {
            files: {
              "index.ts": dedent`
                const capture = x => x;
                console.log(capture(${test.expr}));
              `,
            },
            run: {
              stdout: test.print as string,
            },
            minifySyntax: true,
            minifyWhitespace: true,
            onAfterBundle(api) {
              const capture = api.captureFile("out.js");
              if (capture[0] === JSON.stringify(test.print)) {
                // this is to tell the dev to change the test to use .capture
                // as that is a more strict test (checking literal output)
                // and if the test passes with this, we should be testing for that.
                throw new Error(
                  `Test 'string/${key}': Passes capture test when the test only defines print. Rename .print to .capture on the test to fix this.`,
                );
              }
            },
          },
    );
  }

  itBundled("string/TemplateFolding", {
    files: {
      "entry.js": /* js */ `
        const s1 = "hello";
        \`\${s1} world\`;
        console.log(s1);

        const s2 = \`hello\`;
        console.log(s2);
        const s3 = \`\${s2} world \${s1}\`;
        console.log(s3);

        const s4 = \`\${s1}\${s2}\${s3}\`;
        console.log(s4);

        const s5 = \`👋🌎\`;
        console.log(s5);
        const s6 = \`\${s5} 🌍 \${s1}\`;
        console.log(s6);

        const s7 = \`\${s1}\${s2}\${s3}\${s4}\${s5}\${s6}\`;
        console.log(s7);

        const hexCharacters = "a-f\\d";
        console.log(hexCharacters);
        const match3or4Hex = \`#?[\${hexCharacters}]{3}[\${hexCharacters}]?\`;
        console.log(match3or4Hex);
        const match6or8Hex = \`#?[\${hexCharacters}]{6}([\${hexCharacters}]{2})?\`;
        console.log(match6or8Hex);
        const nonHexChars = new RegExp(\`[^#\${hexCharacters}]\`, "gi");
        console.log(nonHexChars);
        const validHexSize = new RegExp(\`^\${match3or4Hex}\$|^\${match6or8Hex}$\`, "i");
        console.log(validHexSize);

        const INTERNAL = "OOPS";

        function foo() {
          return "NAME";
        }

        console.log(\`k\${foo()}=\${INTERNAL}j\`);
        console.log("d" + INTERNAL);
        console.log(INTERNAL + "l");
        console.log("d" + INTERNAL + "l");

        const CONST_VALUE = "CONST_VALUE";

        function blaBla(a) {
          const { propertyName } = a;
          const condition = \`\${propertyName}.\${CONST_VALUE}AA.WHAT\`;
          return condition;
        }

        console.log(CONST_VALUE);
        console.log(CONST_VALUE === "CONST_VALUE");
      `,
    },
    bundling: false,
    run: {
      stdout: `hello
      hello
      hello world hello
      hellohellohello world hello
      👋🌎
      👋🌎 🌍 hello
      hellohellohello world hellohellohellohello world hello👋🌎👋🌎 🌍 hello
      a-f\d
      #?[a-f\d]{3}[a-f\d]?
      #?[a-f\d]{6}([a-f\d]{2})?
      /[^#a-f\d]/gi
      /^#?[a-f\d]{3}[a-f\d]?$|^#?[a-f\d]{6}([a-f\d]{2})?$/i
      kNAME=OOPSj
      dOOPS
      OOPSl
      dOOPSl
      CONST_VALUE
      true`,
    },
  });
});
