// @ts-check
const assert = require("assert");
const path = require("path");
const fs = require("fs");

const {
    AST_NODE_TYPES,
    AST_TOKEN_TYPES,
} = require("@typescript-eslint/typescript-estree");

/**
 * @typedef {import("eslint").Linter.Config} EslintConfig
 * @typedef  {import("estree").SimpleLiteral} SimpleLiteral
 * @typedef {import("@typescript-eslint/typescript-estree").TSESTree.Node} Node
 * @typedef {import("@typescript-eslint/typescript-estree").TSESTree} TSESTree
 * @typedef {import("@typescript-eslint/typescript-estree").TSESTree.Expression} Expression
 * @typedef {import("@typescript-eslint/typescript-estree").TSESTree.LeftHandSideExpression} LeftHandSideExpression
 * @typedef {import("@typescript-eslint/typescript-estree").TSESTree.CallExpression} CallExpression
 * @typedef {import("@typescript-eslint/experimental-utils/dist/ts-eslint").RuleModule} RuleModule
 * @typedef {import("@typescript-eslint/experimental-utils/dist/ts-eslint").RuleContext} RuleContext
 */

/**
 * @type {EslintConfig}
 */
const config = {
    parser: "@typescript-eslint/parser",
    parserOptions: {
        project: "./tsconfig.json",
        tsconfigRootDir: "./",
        extraFileExtensions: [".ts", ".js", ".tsx", ".jsx"],
    },
    env: {
        //see https://eslint.org/docs/user-guide/configuring#specifying-environments
        browser: true,
        commonjs: true,
        es6: true,
        jquery: true,
    },
    plugins: ["fht-eslint-rules", "@typescript-eslint/tslint", "react"],
    extends: [
        "plugin:@typescript-eslint/recommended",
        "plugin:react/recommended",
    ],
    globals: {
        //true to allow the variable to be overwritten or false to disallow overwriting
        fhtGlobal: false,
        process: false,
        Promise: false,
    },
    rules: {
        "react/forbid-elements": [
            "error",
            {
                forbid: [
                    {
                        element: "style",
                        message: "请使用external scss",
                    },
                ],
            },
        ],
        "@typescript-eslint/tslint/config": [
            "warn",
            {
                lintFile: "./tslint.json", // path to tslint.json of your project
            },
        ],
        // official eslint rules
        // "strict": "error",
        "@typescript-eslint/no-var-requires": "warn",
        "@typescript-eslint/no-use-before-define": "warn",
        "@typescript-eslint/type-annotation-spacing": "off",
        "@typescript-eslint/explicit-function-return-type": "off",
        "@typescript-eslint/no-explicit-any": "off", //as tslint already checks this.
        "@typescript-eslint/no-unused-vars": "off",
        "@typescript-eslint/no-this-alias": "off",
        "@typescript-eslint/indent": "off",
        "@typescript-eslint/camelcase": "off",
        "@typescript-eslint/member-delimiter-style": "off",
        "@typescript-eslint/no-empty-function": "off", //as tslint already checks this.
        "@typescript-eslint/interface-name-prefix": "off",
        "@typescript-eslint/no-empty-interface": "off",
        "@typescript-eslint/no-inferrable-types": "off",
        "@typescript-eslint/ban-types": "off",
        "@typescript-eslint/no-use-before-define": "off",
        "no-implicit-globals": "error",
        "no-global-assign": "error",
        "no-implied-eval": "error",
        "no-undef": "error",
        "no-unused-vars": "off",
        "no-shadow": "warn",
        "no-use-before-define": "off",
        "no-alert": "warn",
        "no-eval": "error",
        "prefer-const": "warn",
        "no-restricted-syntax": [
            "error",
            // {
            //     selector: "BinaryExpression[operator='in']",
            //     message: "不要使用in操作符"
            // },
            {
                selector: "WithStatement",
                message: "never use with statements",
            },
        ],
        "no-restricted-properties": [
            "error",
            {
                //If the object name is omitted, the property is disallowed for all objects:
                property: "__proto__",
                message: "__proto__ is not in W3C standard",
            },
            {
                object: "Object",
                property: "freeze",
                message: "Object.freeze does not work in old IE browsers",
            },
            {
                object: "Object",
                property: "seal",
                message: "Object.seal does not work in old IE browsers",
            },
            {
                object: "document",
                property: "onkeydown",
                message: "不要直接将keydown事件绑定在document上面",
            },

            {
                object: "document",
                property: "onkeyup",
                message: "不要直接将keyup事件绑定在document上面",
            },
        ],
        "no-restricted-globals": [
            "error",
            "screen",
            {
                name: "escape",
                message: "Please use encodeURI or encodeURIComponent instead.",
            },
            {
                name: "unescape",
                message: "Please use decodeURI or decodeURIComponent instead.",
            },
        ],
        "fht-eslint-rules/string-literal-validator": [
            "error",
            {
                /**
                 * @param {Node} node
                 * @param {(n:Node)=>string} printer
                 * @param {string} filePath
                 * @param {RuleContext} context
                 */
                validator: function (node, printer, filePath, context) {
                    /**
                     * @param {string} str
                     * @param {Node} node
                     */
                    function assertStringLiteralRules(str, node) {
                        assert(
                            typeof (str) === "string",
                            "value is not string",
                        );
                        if (str.startsWith("http")) return;
                        if (
                            node.parent.type === AST_NODE_TYPES.CallExpression
                        ) {
                            const calleeString = printer(node.parent.callee);
                            if (calleeString === "require") return;
                            if (
                                /^\w+\.(get|put|delete|post)$/.test(
                                    calleeString,
                                )
                            ) {
                                //express.js router config, ignore.
                                return;
                            }
                        }
                        if (
                            str.startsWith("/") &&
                            str.length > 2 &&
                            !str.includes(".")
                        ) {
                            context.report({
                                node: node,
                                // @ts-ignore
                                message: `不推荐直接将url写死，请使用url_helpers.ts: ${str}`,
                            });
                        }
                        if (/[\w_]{3,}\.(gif|png|jpeg|jpg)/i.test(str)) {
                            if (
                                node.parent.type !==
                                AST_NODE_TYPES.ImportDeclaration
                            ) {
                                context.report({
                                    node: node,
                                    // @ts-ignore
                                    message: `请使用静态生成后的图片或者静态文件路径变量: ${str}`,
                                });
                            }
                        }
                    }

                    /** @type {string} */
                    if (node.type === "Literal") {
                        const raw = node.raw;
                        assert(raw, "raw is falsy");
                        const isStringLiteral =
                            raw.startsWith("'") || raw.startsWith('"');
                        if (isStringLiteral) {
                            const value = node.value;
                            if (typeof (value) === "string") {
                                assertStringLiteralRules(value, node);
                            }
                        }
                    } else if (node.type === "TemplateLiteral") {
                        const value = node.quasis.map(i => i.value.raw)[0];
                        if (value) {
                            assertStringLiteralRules(value, node);
                        }
                    }
                },
            },
        ],
        "fht-eslint-rules/function-argument-validator": [
            "error",
            {
                /**
                 *
                 * @param {LeftHandSideExpression} callee
                 * @param {Expression[]} args
                 * @param {(n:Node)=>string} printer
                 * @param {string} filePath
                 */
                validator: function (callee, args, printer, filePath) {
                    if (
                        callee.type === AST_NODE_TYPES.MemberExpression &&
                        callee.property &&
                        callee.property.type === AST_NODE_TYPES.Identifier
                    ) {
                        if (callee.property.name === "indexOf") {
                            const firstArg = args[0];
                            if (firstArg) {
                                if (firstArg.type === AST_NODE_TYPES.Literal) {
                                    const value = firstArg.value;
                                    if (typeof (value) === "string") {
                                        if (/^[a-zA-Z0-9]{3,}$/.test(value)) {
                                            throw new Error(
                                                `do not test for raw strings: ${value}`,
                                            );
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return true;
                },
            },
        ],
        "fht-eslint-rules/no-property-assignment-to-window": "error",
        "fht-eslint-rules/never-compare-text-with-string": "error",
    },
    settings: {
        polyfills: ["Promise"],
    },
};

module.exports = config;
