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

const TASKS = [
  "task",
  "restartableTask",
  "dropTask",
  "keepLatestTask",
  "enqueueTask",
];
const WAIT_FOR = "waitFor";
const WAIT_FOR_D = `@${WAIT_FOR}`;
const ERR_TASK_BEFORE_WAIT_FOR =
  "@task has to be always before @waitFor - https://github.com/emberjs/ember-test-waiters#waitfor-function";

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

type Options = [];
type MessageIds = "task-before-wait-for";

export const decoratorOrder = createRule<Options, MessageIds>({
  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 &&
            waitForPosition > -1 &&
            taskPosition > waitForPosition
          ) {
            context.report({
              node: node.decorators[taskPosition],
              messageId: "task-before-wait-for",
              *fix(fixer) {
                // @ts-ignore
                const swap = node.decorators[taskPosition].expression.name;
                yield fixer.replaceText(
                  node.decorators[taskPosition],
                  WAIT_FOR_D,
                );
                yield fixer.replaceText(
                  node.decorators[waitForPosition],
                  `@${swap}`,
                );
              },
            });
            return;
          }
        }
      },
    };
  },
  name: "decorator-order",
  meta: {
    docs: {
      description: "@task should be before @waitFor",
    },
    messages: {
      "task-before-wait-for": ERR_TASK_BEFORE_WAIT_FOR,
    },
    type: "suggestion",
    schema: [],
    fixable: "code",
  },
  defaultOptions: [],
});
