// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import type {TSESTree} from '@typescript-eslint/utils';

import {createRule} from './utils/ruleCreator.ts';

export default createRule({
  name: 'screenshot-assertion-in-it-screenshot',
  meta: {
    type: 'problem',
    docs: {
      description: 'ensure screenshots are asserted in an itScreenshot block',
      category: 'Possible Errors',
    },
    messages: {
      itScreenshotRequired: 'A screenshot assertion must be within an itScreenshot() test, not an it() test.',
    },
    fixable: 'code',
    schema: []  // no options
  },
  defaultOptions: [],
  create: function(context) {
    const SCREENSHOT_ASSERTION_FUNCTIONS =
        new Set(['assertElementScreenshotUnchanged', 'assertPageScreenshotUnchanged']);

    function nodeIsFunctionCallToCheck(node) {
      if (node.expression.type === 'CallExpression') {
        return true;
      }

      if (node.expression.type === 'AwaitExpression') {
        return node.expression.argument.type === 'CallExpression';
      }

      return false;
    }
    function getNameOfFunctionToCheck(node) {
      if (node.type !== 'ExpressionStatement') {
        return null;
      }
      if (!nodeIsFunctionCallToCheck(node)) {
        return null;
      }

      let nameOfCalledFunction = '';
      if (node.expression.type === 'CallExpression') {
        nameOfCalledFunction = node.expression.callee.name;
      } else if (node.expression.type === 'AwaitExpression') {
        nameOfCalledFunction = node.expression.argument.callee.name;
      }
      return nameOfCalledFunction || null;
    }

    function findItParentForNode(node) {
      if (!node?.parent) {
        return null;
      }

      const parent = node.parent;

      if (parent.type === 'Program') {
        return null;
      }

      if (parent.type === 'ExpressionStatement' && parent.expression.type === 'CallExpression' &&
          parent.expression.callee?.name === 'it') {
        return parent;
      }

      return findItParentForNode(node.parent);
    }

    function checkForScreenshotCalls(bodyNodes) {
      for (const node of bodyNodes) {
        const functionCallName = getNameOfFunctionToCheck(node);
        if (!functionCallName) {
          continue;
        }
        if (SCREENSHOT_ASSERTION_FUNCTIONS.has(functionCallName)) {
          context.report({
            node,
            messageId: 'itScreenshotRequired',
            fix(fixer) {
              const itExpression = findItParentForNode(node);
              const callee = itExpression?.expression?.callee;
              if (!callee) {
                return [];
              }

              return [fixer.replaceText(callee, 'itScreenshot')];
            }
          });
        }
      }
    }

    return {
      'CallExpression[callee.type="Identifier"][callee.name="it"]'(node: TSESTree.CallExpression) {
        const testCallback = node.arguments[1];
        if (testCallback.type === 'ArrowFunctionExpression' || testCallback.type === 'FunctionExpression') {
          if (testCallback.body.type === 'BlockStatement') {
            checkForScreenshotCalls(testCallback.body.body);
          }
        }
      }
    };
  }
});
