/**
 * @fileoverview Require or disallow newline at the end of files
 * @author Nodeca Team <https://github.com/nodeca>
 * @deprecated in ESLint v8.53.0
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../types').Rule.RuleModule} */
module.exports = {
	meta: {
		deprecated: {
			message: "Formatting rules are being moved out of ESLint core.",
			url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
			deprecatedSince: "8.53.0",
			availableUntil: "10.0.0",
			replacedBy: [
				{
					message:
						"ESLint Stylistic now maintains deprecated stylistic core rules.",
					url: "https://eslint.style/guide/migration",
					plugin: {
						name: "@stylistic/eslint-plugin",
						url: "https://eslint.style",
					},
					rule: {
						name: "eol-last",
						url: "https://eslint.style/rules/eol-last",
					},
				},
			],
		},
		type: "layout",

		docs: {
			description: "Require or disallow newline at the end of files",
			recommended: false,
			url: "https://eslint.org/docs/latest/rules/eol-last",
		},

		fixable: "whitespace",

		schema: [
			{
				enum: ["always", "never", "unix", "windows"],
			},
		],

		messages: {
			missing: "Newline required at end of file but not found.",
			unexpected: "Newline not allowed at end of file.",
		},
	},
	create(context) {
		//--------------------------------------------------------------------------
		// Public
		//--------------------------------------------------------------------------

		return {
			Program: function checkBadEOF(node) {
				const sourceCode = context.sourceCode,
					src = sourceCode.getText(),
					lastLine = sourceCode.lines.at(-1),
					location = {
						column: lastLine.length,
						line: sourceCode.lines.length,
					},
					LF = "\n",
					CRLF = `\r${LF}`,
					endsWithNewline = src.endsWith(LF);

				/*
				 * Empty source is always valid: No content in file so we don't
				 * need to lint for a newline on the last line of content.
				 */
				if (!src.length) {
					return;
				}

				let mode = context.options[0] || "always",
					appendCRLF = false;

				if (mode === "unix") {
					// `"unix"` should behave exactly as `"always"`
					mode = "always";
				}
				if (mode === "windows") {
					// `"windows"` should behave exactly as `"always"`, but append CRLF in the fixer for backwards compatibility
					mode = "always";
					appendCRLF = true;
				}
				if (mode === "always" && !endsWithNewline) {
					// File is not newline-terminated, but should be
					context.report({
						node,
						loc: location,
						messageId: "missing",
						fix(fixer) {
							return fixer.insertTextAfterRange(
								[0, src.length],
								appendCRLF ? CRLF : LF,
							);
						},
					});
				} else if (mode === "never" && endsWithNewline) {
					const secondLastLine = sourceCode.lines.at(-2);

					// File is newline-terminated, but shouldn't be
					context.report({
						node,
						loc: {
							start: {
								line: sourceCode.lines.length - 1,
								column: secondLastLine.length,
							},
							end: { line: sourceCode.lines.length, column: 0 },
						},
						messageId: "unexpected",
						fix(fixer) {
							const finalEOLs = /(?:\r?\n)+$/u,
								match = finalEOLs.exec(sourceCode.text),
								start = match.index,
								end = sourceCode.text.length;

							return fixer.replaceTextRange([start, end], "");
						},
					});
				}
			},
		};
	},
};
