/*
	MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[Error, EXPECTED_ANY, string]>} ObjectDeserializerContext */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[Error, EXPECTED_ANY, string]>} ObjectSerializerContext */

const makeSerializable = require("../util/makeSerializable");

const CONTEXT = 20;

class JSONParseError extends SyntaxError {
	/**
	 * @param {Error} err err
	 * @param {EXPECTED_ANY} raw raw
	 * @param {string} txt text
	 */
	constructor(err, raw, txt) {
		let originalMessage = err.message;
		/** @type {string} */
		let message;
		/** @type {number} */
		let position;

		if (typeof raw !== "string") {
			message = `Cannot parse ${Array.isArray(raw) && raw.length === 0 ? "an empty array" : String(raw)}`;
			position = 0;
		} else if (!txt) {
			message = `${originalMessage} while parsing empty string`;
			position = 0;
		} else {
			// Node 20 puts single quotes around the token and a comma after it
			const UNEXPECTED_TOKEN = /^Unexpected token '?(.)'?(,)? /i;
			const badTokenMatch = originalMessage.match(UNEXPECTED_TOKEN);
			const badIndexMatch = originalMessage.match(/ position\s+(\d+)/i);

			if (badTokenMatch) {
				const h = badTokenMatch[1].charCodeAt(0).toString(16).toUpperCase();
				const hex = `0x${h.length % 2 ? "0" : ""}${h}`;

				originalMessage = originalMessage.replace(
					UNEXPECTED_TOKEN,
					`Unexpected token ${JSON.stringify(badTokenMatch[1])} (${hex})$2 `
				);
			}

			/** @type {number | undefined} */
			let errIdx;

			if (badIndexMatch) {
				errIdx = Number(badIndexMatch[1]);
			} else if (
				// doesn't happen in Node 22+
				/^Unexpected end of JSON.*/i.test(originalMessage)
			) {
				errIdx = txt.length - 1;
			}

			if (errIdx === undefined) {
				message = `${originalMessage} while parsing '${txt.slice(0, CONTEXT * 2)}'`;
				position = 0;
			} else {
				const start = errIdx <= CONTEXT ? 0 : errIdx - CONTEXT;
				const end =
					errIdx + CONTEXT >= txt.length ? txt.length : errIdx + CONTEXT;
				const slice = `${start ? "..." : ""}${txt.slice(start, end)}${end === txt.length ? "" : "..."}`;

				message = `${originalMessage} while parsing ${txt === slice ? "" : "near "}${JSON.stringify(slice)}`;
				position = errIdx;
			}
		}

		super(message);

		/** @type {string} */
		this.name = "JSONParseError";
		/** @type {string | undefined} */
		this.stack = undefined;
		/** @type {Error} */
		this.systemError = err;
		/** @type {EXPECTED_ANY} */
		this.raw = raw;
		/** @type {string} */
		this.txt = txt;
		/** @type {number} */
		this.position = position;
	}

	/**
	 * Serializes this instance into the provided serializer context.
	 * @param {ObjectSerializerContext} context context
	 */
	serialize(context) {
		context.write(this.systemError).write(this.raw).write(this.txt);
	}

	/**
	 * Restores this instance from the provided deserializer context.
	 * @param {ObjectDeserializerContext} context context
	 * @returns {JSONParseError} DelegatedModule
	 */
	static deserialize(context) {
		const systemError = context.read();
		const c1 = context.rest;
		const raw = c1.read();
		const c2 = c1.rest;
		return new JSONParseError(systemError, raw, c2.read());
	}
}

makeSerializable(JSONParseError, "webpack/lib/errors/JSONParseError");

module.exports = JSONParseError;
