import { parseSync, traverse } from "@babel/core";
import { shouldTransform } from "../src/util.ts";

function getPath(input, parserOpts = {}) {
  let targetPath;
  traverse(
    parseSync(input, {
      parserOpts: {
        plugins: [
          "classPrivateMethods",
          "classPrivateProperties",
          "classProperties",
          ...(parserOpts.plugins || []),
        ],
        ...parserOpts,
      },
      filename: "example.js",
    }),
    {
      "OptionalMemberExpression|OptionalCallExpression"(path) {
        targetPath = path;
        path.stop();
      },
      noScope: true,
    },
  );
  return targetPath;
}

describe("shouldTransform", () => {
  const positiveCases = [
    "fn?.(...[], 0)",
    "fn?.(...[], ...[])",
    "fn?.(0, ...[], ...[])",
    "a?.b(...[], 0)",
    "a?.[b](...[], 0)",
    "a?.b?.(...[], 0)",
    "fn?.(0, ...[], 0)",
    "a?.b?.(0, ...[], 0)",
    "(a?.b)?.(...[], 0)",
    "a?.b.c?.(...[], 0)",
    "class C { #c; p = obj?.#c(...[], 0) }",
    "class C { #c; p = obj.#c?.(...[], 0) }",
  ];

  const negativeCases = [
    "a?.b",
    "fn?.(1)",
    "fn?.(...[])",
    "fn?.(1, ...[])",
    "a?.b(...[])",
    "a?.()(...[], 1)", // optional call under optional call is not affected
    "(a?.b)(...[], 1)", // not an optional call
    "a?.b.c(...[], 1)",
    "a?.[fn?.(...[], 0)]", // optional chain in property will be handled when traversed
    "a?.(fn?.(...[], 0))", // optional chain in arguments will be handled when traversed
    "class C { #c; p = obj?.#c(...[]) }",
  ];

  const typescriptPositiveCases = [
    "(a?.(...[], 0) as any)?.b",
    "(a?.(...[], 0) as any)?.()",
  ];

  const typescriptNegativeCases = ["(a?.b as any)(...[], 0)"];

  describe("default parser options", () => {
    test.each(positiveCases)(
      "shouldTransform(a?.b in %p) should return true",
      input => {
        expect(shouldTransform(getPath(input))).toBe(true);
      },
    );
    test.each(negativeCases)(
      "shouldTransform(a?.b in %p) should return false",
      input => {
        expect(shouldTransform(getPath(input))).toBe(false);
      },
    );
  });

  describe("createParenthesizedExpressions", () => {
    test.each(positiveCases)(
      "shouldTransform(a?.b in %p with { createParenthesizedExpressions: true }) should return true",
      input => {
        const parserOpts = { createParenthesizedExpressions: true };
        expect(shouldTransform(getPath(input, parserOpts))).toBe(true);
      },
    );
    test.each(negativeCases)(
      "shouldTransform(a?.b in %p with { createParenthesizedExpressions: true }) should return false",
      input => {
        const parserOpts = { createParenthesizedExpressions: true };
        expect(shouldTransform(getPath(input, parserOpts))).toBe(false);
      },
    );
  });

  describe("plugins: [typescript]", () => {
    test.each(positiveCases.concat(typescriptPositiveCases))(
      "shouldTransform(a?.b in %p with { plugins: [typescript] }) should return true",
      input => {
        const parserOpts = { plugins: ["typescript"] };
        expect(shouldTransform(getPath(input, parserOpts))).toBe(true);
      },
    );
    test.each(negativeCases.concat(typescriptNegativeCases))(
      "shouldTransform(a?.b in %p with { plugins: [typescript] }) should return false",
      input => {
        const parserOpts = { plugins: ["typescript"] };
        expect(shouldTransform(getPath(input, parserOpts))).toBe(false);
      },
    );
  });
});
