import {
  AST_NODE_TYPES,
  ESLintUtils,
  TSESTree,
} from "@typescript-eslint/utils";

const TASKS = [
  "task",
  "restartableTask",
  "dropTask",
  "keepLatestTask",
  "enqueueTask",
];
const WAIT_FOR = "waitFor";
const WAIT_FOR_D = `@${WAIT_FOR}`;
const IMPORT_D = `import { waitFor } from '@ember/test-waiters';`;
const ERR_TASK_WITHOUT_WAIT_FOR =
  "@task usage without @waitFor can be a cause of promises not being awaited for in tests. That is why every usage of @task should be accompanied with @waitFor - https://github.com/emberjs/ember-test-waiters#waitfor-function";

const createRule = ESLintUtils.RuleCreator((name) => `decorator-presence`);

// Type: RuleModule<"uppercase", ...>
export const decoratorPresence = createRule({
  create(context) {
    return {
      ["MethodDefinition:has([decorators])"](node: TSESTree.Node) {
        if (node.type === AST_NODE_TYPES.MethodDefinition) {
          // TODO: No idea why, but this does not work: https://typescript-eslint.io/developers/custom-rules#explicit-node-types
          // @ts-ignore
          const decorators = node.decorators.map((d) => d.expression.name);
          const taskPosition = decorators.findIndex((elm) =>
            TASKS.includes(elm),
          );
          const waitForPosition = decorators.findIndex(
            (elm) => elm === WAIT_FOR,
          );
          if (taskPosition > -1) {
            if (waitForPosition === -1) {
              context.report({
                node,
                messageId: "task-without-wait-for",
                *fix(fixer) {
                  yield fixer.insertTextAfter(
                    node.decorators[taskPosition],
                    " ",
                  );
                  yield fixer.insertTextAfter(
                    node.decorators[taskPosition],
                    WAIT_FOR_D,
                  );
                  const top = context?.getAncestors()?.[0];
                  // @ts-ignore
                  const hasImport = top?.body.some((elm) => {
                    return (
                      elm.type === "ImportDeclaration" &&
                      elm.specifiers &&
                      elm.specifiers.some(
                        // @ts-ignore
                        (spec) => spec?.local?.name === "waitFor",
                      )
                    );
                  });
                  if (!hasImport) {
                    yield fixer.insertTextBefore(top, IMPORT_D);
                  }
                },
              });
              return;
            }
          }
        }
      },
    };
  },
  name: "decorator-presence",
  meta: {
    docs: {
      description: "@task should be accompanied by @waitFor",
    },
    messages: {
      "task-without-wait-for": ERR_TASK_WITHOUT_WAIT_FOR,
    },
    type: "suggestion",
    schema: [],
    fixable: "code",
  },
  defaultOptions: [],
});
