import { promises as fs } from "fs";
import chalk from "next/dist/compiled/chalk";
import path from "path";
import findUp from "next/dist/compiled/find-up";
import semver from "next/dist/compiled/semver";
import * as CommentJson from "next/dist/compiled/comment-json";
import { formatResults } from "./customFormatter";
import { writeDefaultConfig } from "./writeDefaultConfig";
import { hasEslintConfiguration } from "./hasEslintConfiguration";
import { writeOutputFile } from "./writeOutputFile";
import { ESLINT_PROMPT_VALUES } from "../constants";
import { existsSync, findPagesDir } from "../find-pages-dir";
import { installDependencies } from "../install-dependencies";
import { hasNecessaryDependencies } from "../has-necessary-dependencies";
import * as Log from "../../build/output/log";
import isError, { getProperError } from "../is-error";
import { getPkgManager } from "../helpers/get-pkg-manager";
// 0 is off, 1 is warn, 2 is error. See https://eslint.org/docs/user-guide/configuring/rules#configuring-rules
const VALID_SEVERITY = [
    "off",
    "warn",
    "error"
];
function isValidSeverity(severity) {
    return VALID_SEVERITY.includes(severity);
}
const requiredPackages = [
    {
        file: "eslint",
        pkg: "eslint",
        exportsRestrict: false
    },
    {
        file: "eslint-config-next",
        pkg: "eslint-config-next",
        exportsRestrict: false
    }
];
async function cliPrompt() {
    console.log(chalk.bold(`${chalk.cyan("?")} How would you like to configure ESLint? https://nextjs.org/docs/basic-features/eslint`));
    try {
        const cliSelect = (await Promise.resolve(require("next/dist/compiled/cli-select"))).default;
        const { value  } = await cliSelect({
            values: ESLINT_PROMPT_VALUES,
            valueRenderer: ({ title , recommended  }, selected)=>{
                const name = selected ? chalk.bold.underline.cyan(title) : title;
                return name + (recommended ? chalk.bold.yellow(" (recommended)") : "");
            },
            selected: chalk.cyan("❯ "),
            unselected: "  "
        });
        return {
            config: (value == null ? void 0 : value.config) ?? null
        };
    } catch  {
        return {
            config: null
        };
    }
}
async function lint(baseDir, lintDirs, eslintrcFile, pkgJsonPath, hasAppDir, { lintDuringBuild =false , eslintOptions =null , reportErrorsOnly =false , maxWarnings =-1 , formatter =null , outputFile =null  }) {
    try {
        var _mod_CLIEngine, _ESLint_getErrorResults;
        // Load ESLint after we're sure it exists:
        const deps = await hasNecessaryDependencies(baseDir, requiredPackages);
        const packageManager = getPkgManager(baseDir);
        if (deps.missing.some((dep)=>dep.pkg === "eslint")) {
            Log.error(`ESLint must be installed${lintDuringBuild ? " in order to run during builds:" : ":"} ${chalk.bold.cyan((packageManager === "yarn" ? "yarn add --dev" : packageManager === "pnpm" ? "pnpm install --save-dev" : "npm install --save-dev") + " eslint")}`);
            return null;
        }
        const mod = await Promise.resolve(require(deps.resolved.get("eslint")));
        const { ESLint  } = mod;
        let eslintVersion = (ESLint == null ? void 0 : ESLint.version) ?? ((_mod_CLIEngine = mod.CLIEngine) == null ? void 0 : _mod_CLIEngine.version);
        if (!eslintVersion || semver.lt(eslintVersion, "7.0.0")) {
            return `${chalk.red("error")} - Your project has an older version of ESLint installed${eslintVersion ? " (" + eslintVersion + ")" : ""}. Please upgrade to ESLint version 7 or above`;
        }
        let options = {
            useEslintrc: true,
            baseConfig: {},
            errorOnUnmatchedPattern: false,
            extensions: [
                ".js",
                ".jsx",
                ".ts",
                ".tsx"
            ],
            cache: true,
            ...eslintOptions
        };
        let eslint = new ESLint(options);
        let nextEslintPluginIsEnabled = false;
        const nextRulesEnabled = new Map();
        for (const configFile of [
            eslintrcFile,
            pkgJsonPath
        ]){
            var _completeConfig_plugins;
            if (!configFile) continue;
            const completeConfig = await eslint.calculateConfigForFile(configFile);
            if ((_completeConfig_plugins = completeConfig.plugins) == null ? void 0 : _completeConfig_plugins.includes("@next/next")) {
                nextEslintPluginIsEnabled = true;
                for (const [name, [severity]] of Object.entries(completeConfig.rules)){
                    if (!name.startsWith("@next/next/")) {
                        continue;
                    }
                    if (typeof severity === "number" && severity >= 0 && severity < VALID_SEVERITY.length) {
                        nextRulesEnabled.set(name, VALID_SEVERITY[severity]);
                    } else if (typeof severity === "string" && isValidSeverity(severity)) {
                        nextRulesEnabled.set(name, severity);
                    }
                }
                break;
            }
        }
        const pagesDir = findPagesDir(baseDir, hasAppDir).pagesDir;
        const pagesDirRules = pagesDir ? [
            "@next/next/no-html-link-for-pages"
        ] : [];
        if (nextEslintPluginIsEnabled) {
            let updatedPagesDir = false;
            for (const rule of pagesDirRules){
                var _options_baseConfig_rules, _options_baseConfig_rules1;
                if (!((_options_baseConfig_rules = options.baseConfig.rules) == null ? void 0 : _options_baseConfig_rules[rule]) && !((_options_baseConfig_rules1 = options.baseConfig.rules) == null ? void 0 : _options_baseConfig_rules1[rule.replace("@next/next", "@next/babel-plugin-next")])) {
                    if (!options.baseConfig.rules) {
                        options.baseConfig.rules = {};
                    }
                    options.baseConfig.rules[rule] = [
                        1,
                        pagesDir
                    ];
                    updatedPagesDir = true;
                }
            }
            if (updatedPagesDir) {
                eslint = new ESLint(options);
            }
        } else {
            Log.warn("");
            Log.warn("The Next.js plugin was not detected in your ESLint configuration. See https://nextjs.org/docs/basic-features/eslint#migrating-existing-config");
        }
        const lintStart = process.hrtime();
        let results = await eslint.lintFiles(lintDirs);
        let selectedFormatter = null;
        if (options.fix) await ESLint.outputFixes(results);
        if (reportErrorsOnly) results = await ESLint.getErrorResults(results) // Only return errors if --quiet flag is used
        ;
        if (formatter) selectedFormatter = await eslint.loadFormatter(formatter);
        const formattedResult = formatResults(baseDir, results, selectedFormatter == null ? void 0 : selectedFormatter.format);
        const lintEnd = process.hrtime(lintStart);
        const totalWarnings = results.reduce((sum, file)=>sum + file.warningCount, 0);
        if (outputFile) await writeOutputFile(outputFile, formattedResult.output);
        return {
            output: formattedResult.outputWithMessages,
            isError: ((_ESLint_getErrorResults = ESLint.getErrorResults(results)) == null ? void 0 : _ESLint_getErrorResults.length) > 0 || maxWarnings >= 0 && totalWarnings > maxWarnings,
            eventInfo: {
                durationInSeconds: lintEnd[0],
                eslintVersion: eslintVersion,
                lintedFilesCount: results.length,
                lintFix: !!options.fix,
                nextEslintPluginVersion: nextEslintPluginIsEnabled && deps.resolved.has("eslint-config-next") ? require(path.join(path.dirname(deps.resolved.get("eslint-config-next")), "package.json")).version : null,
                nextEslintPluginErrorsCount: formattedResult.totalNextPluginErrorCount,
                nextEslintPluginWarningsCount: formattedResult.totalNextPluginWarningCount,
                nextRulesEnabled: Object.fromEntries(nextRulesEnabled)
            }
        };
    } catch (err) {
        if (lintDuringBuild) {
            Log.error(`ESLint: ${isError(err) && err.message ? err.message.replace(/\n/g, " ") : err}`);
            return null;
        } else {
            throw getProperError(err);
        }
    }
}
export async function runLintCheck(baseDir, lintDirs, hasAppDir, opts) {
    const { lintDuringBuild =false , eslintOptions =null , reportErrorsOnly =false , maxWarnings =-1 , formatter =null , outputFile =null , strict =false  } = opts;
    try {
        // Find user's .eslintrc file
        // See: https://eslint.org/docs/user-guide/configuring/configuration-files#configuration-file-formats
        const eslintrcFile = await findUp([
            ".eslintrc.js",
            ".eslintrc.cjs",
            ".eslintrc.yaml",
            ".eslintrc.yml",
            ".eslintrc.json",
            ".eslintrc"
        ], {
            cwd: baseDir
        }) ?? null;
        const pkgJsonPath = await findUp("package.json", {
            cwd: baseDir
        }) ?? null;
        let packageJsonConfig = null;
        if (pkgJsonPath) {
            const pkgJsonContent = await fs.readFile(pkgJsonPath, {
                encoding: "utf8"
            });
            packageJsonConfig = CommentJson.parse(pkgJsonContent);
        }
        const config = await hasEslintConfiguration(eslintrcFile, packageJsonConfig);
        let deps;
        if (config.exists) {
            // Run if ESLint config exists
            return await lint(baseDir, lintDirs, eslintrcFile, pkgJsonPath, hasAppDir, {
                lintDuringBuild,
                eslintOptions,
                reportErrorsOnly,
                maxWarnings,
                formatter,
                outputFile
            });
        } else {
            // Display warning if no ESLint configuration is present inside
            // config file during "next build", no warning is shown when
            // no eslintrc file is present
            if (lintDuringBuild) {
                if (config.emptyPkgJsonConfig || config.emptyEslintrc) {
                    Log.warn(`No ESLint configuration detected. Run ${chalk.bold.cyan("next lint")} to begin setup`);
                }
                return null;
            } else {
                // Ask user what config they would like to start with for first time "next lint" setup
                const { config: selectedConfig  } = strict ? ESLINT_PROMPT_VALUES.find((opt)=>opt.title === "Strict") : await cliPrompt();
                if (selectedConfig == null) {
                    // Show a warning if no option is selected in prompt
                    Log.warn("If you set up ESLint yourself, we recommend adding the Next.js ESLint plugin. See https://nextjs.org/docs/basic-features/eslint#migrating-existing-config");
                    return null;
                } else {
                    // Check if necessary deps installed, and install any that are missing
                    deps = await hasNecessaryDependencies(baseDir, requiredPackages);
                    if (deps.missing.length > 0) await installDependencies(baseDir, deps.missing, true);
                    // Write default ESLint config.
                    // Check for /pages and src/pages is to make sure this happens in Next.js folder
                    if (existsSync(path.join(baseDir, "pages")) || existsSync(path.join(baseDir, "src/pages"))) {
                        await writeDefaultConfig(baseDir, config, selectedConfig, eslintrcFile, pkgJsonPath, packageJsonConfig);
                    }
                }
                Log.ready(`ESLint has successfully been configured. Run ${chalk.bold.cyan("next lint")} again to view warnings and errors.`);
                return null;
            }
        }
    } catch (err) {
        throw err;
    }
}

//# sourceMappingURL=runLintCheck.js.map