/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

"use strict";

const { dirname, join } = require("path");

const eslintBasePath = dirname(require.resolve("eslint"));

const noredeclarePath = join(eslintBasePath, "rules/no-redeclare.js");
const baseRule = require(noredeclarePath);
const astUtils = require(join(eslintBasePath, "rules/utils/ast-utils.js"));

// Hack alert: our eslint env is pretty confused about `require` and
// `loader` for devtools modules - so ignore it for now.
// See bug 1812547
const gIgnoredImports = new Set(["loader", "require"]);

/**
 * Create a trap for a call to `report` that the original rule is
 * trying to make on `context`.
 *
 * Returns a function that forwards to `report` but provides a fixer
 * for redeclared imports that just removes those imports.
 *
 * @return {function}
 */
function trapReport(context) {
  return function (obj) {
    let declarator = obj.node.parent;
    while (
      declarator &&
      declarator.parent &&
      declarator.type != "VariableDeclarator"
    ) {
      declarator = declarator.parent;
    }
    if (
      declarator &&
      declarator.type == "VariableDeclarator" &&
      declarator.id.type == "ObjectPattern" &&
      declarator.init.type == "CallExpression"
    ) {
      let initialization = declarator.init;
      if (
        astUtils.isSpecificMemberAccess(
          initialization.callee,
          "ChromeUtils",
          /^import(ESModule|)$/
        )
      ) {
        // Hack alert: our eslint env is pretty confused about `require` and
        // `loader` for devtools modules - so ignore it for now.
        // See bug 1812547
        if (gIgnoredImports.has(obj.node.name)) {
          return;
        }
        // OK, we've got something we can fix. But we should be careful in case
        // there are multiple imports being destructured.
        // Do the easy (and common) case first - just one property:
        if (declarator.id.properties.length == 1) {
          context.report({
            node: declarator.parent,
            messageId: "duplicateImport",
            data: {
              name: declarator.id.properties[0].key.name,
            },
            fix(fixer) {
              return fixer.remove(declarator.parent);
            },
          });
          return;
        }

        // OK, figure out which import is duplicated here:
        let node = obj.node.parent;
        // Then remove a comma after it, or a comma before
        // if there's no comma after it.
        let sourceCode = context.getSourceCode();
        let rangeToRemove = node.range;
        let tokenAfter = sourceCode.getTokenAfter(node);
        let tokenBefore = sourceCode.getTokenBefore(node);
        if (astUtils.isCommaToken(tokenAfter)) {
          rangeToRemove[1] = tokenAfter.range[1];
        } else if (astUtils.isCommaToken(tokenBefore)) {
          rangeToRemove[0] = tokenBefore.range[0];
        }
        context.report({
          node,
          messageId: "duplicateImport",
          data: {
            name: node.key.name,
          },
          fix(fixer) {
            return fixer.removeRange(rangeToRemove);
          },
        });
        return;
      }
    }
    if (context.options[0]?.errorForNonImports) {
      // Report the result from no-redeclare - we can't autofix it.
      // This can happen for other redeclaration issues, e.g. naming
      // variables in a way that conflicts with builtins like "URL" or
      // "escape".
      context.report(obj);
    }
  };
}

module.exports = {
  meta: {
    docs: {
      url: "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-redeclare-with-import-autofix.html",
    },
    messages: {
      ...baseRule.meta.messages,
      duplicateImport:
        "The import of '{{ name }}' is redundant with one set up earlier (e.g. head.js or the browser window environment). It should be removed.",
    },
    schema: [
      {
        type: "object",
        properties: {
          errorForNonImports: {
            type: "boolean",
            default: true,
          },
        },
        additionalProperties: false,
      },
    ],
    type: "suggestion",
    fixable: "code",
  },

  create(context) {
    // Test modules get the browser env applied wrongly in some cases,
    // don't try and remove imports there. This works out of the box
    // for sys.mjs modules because eslint won't check builtinGlobals
    // for the no-redeclare rule.
    if (context.getFilename().endsWith(".jsm")) {
      return {};
    }
    let newOptions = [{ builtinGlobals: true }];
    const contextForBaseRule = Object.create(context, {
      report: {
        value: trapReport(context),
        writable: false,
      },
      options: {
        value: newOptions,
      },
    });
    return baseRule.create(contextForBaseRule);
  },
};
