import { Ono, ono } from "@jsdevtools/ono";
import { join, win32 } from "node:path";
import { load } from "js-yaml";
import fs from "fs";
//#region src/util/convert-path-to-posix.ts
function convertPathToPosix(filePath) {
	if (filePath.startsWith("\\\\?\\")) return filePath;
	return filePath.replaceAll("\\", "/");
}
//#endregion
//#region src/util/is-windows.ts
const isWindowsConst = /^win/.test(globalThis.process ? globalThis.process.platform : "");
const isWindows = () => isWindowsConst;
//#endregion
//#region src/util/url.ts
const forwardSlashPattern = /\//g;
const protocolPattern = /^(\w{2,}):\/\//i;
const urlEncodePatterns = [[/\?/g, "%3F"], [/#/g, "%23"]];
const urlDecodePatterns = [
	/%23/g,
	"#",
	/%24/g,
	"$",
	/%26/g,
	"&",
	/%2C/g,
	",",
	/%40/g,
	"@"
];
/**
* Returns resolved target URL relative to a base URL in a manner similar to that of a Web browser resolving an anchor tag HREF.
*
* @returns
*/
function resolve(from, to) {
	const fromUrl = new URL(convertPathToPosix(from), "resolve://");
	const resolvedUrl = new URL(convertPathToPosix(to), fromUrl);
	const endSpaces = to.match(/(\s*)$/)?.[1] || "";
	if (resolvedUrl.protocol === "resolve:") {
		const { hash, pathname, search } = resolvedUrl;
		return pathname + search + hash + endSpaces;
	}
	return resolvedUrl.toString() + endSpaces;
}
/**
* Returns the current working directory (in Node) or the current page URL (in browsers).
*
* @returns
*/
function cwd() {
	if (typeof window !== "undefined") return location.href;
	const path = process.cwd();
	const lastChar = path.slice(-1);
	if (lastChar === "/" || lastChar === "\\") return path;
	else return path + "/";
}
/**
* Returns the protocol of the given URL, or `undefined` if it has no protocol.
*
* @param path
* @returns
*/
function getProtocol(path) {
	const match = protocolPattern.exec(path || "");
	if (match) return match[1].toLowerCase();
}
/**
* Returns the lowercased file extension of the given URL,
* or an empty string if it has no extension.
*
* @param path
* @returns
*/
function getExtension(path) {
	const lastDot = path.lastIndexOf(".");
	if (lastDot > -1) return stripQuery(path.substr(lastDot).toLowerCase());
	return "";
}
/**
* Removes the query, if any, from the given path.
*
* @param path
* @returns
*/
function stripQuery(path) {
	const queryIndex = path.indexOf("?");
	if (queryIndex > -1) path = path.substr(0, queryIndex);
	return path;
}
/**
* Returns the hash (URL fragment), of the given path.
* If there is no hash, then the root hash ("#") is returned.
*
* @param path
* @returns
*/
function getHash(path) {
	if (!path) return "#";
	const hashIndex = path.indexOf("#");
	if (hashIndex > -1) return path.substring(hashIndex);
	return "#";
}
/**
* Removes the hash (URL fragment), if any, from the given path.
*
* @param path
* @returns
*/
function stripHash(path) {
	if (!path) return "";
	const hashIndex = path.indexOf("#");
	if (hashIndex > -1) path = path.substring(0, hashIndex);
	return path;
}
/**
* Determines whether the given path is a filesystem path.
* This includes "file://" URLs.
*
* @param path
* @returns
*/
function isFileSystemPath(path) {
	if (typeof window !== "undefined" || typeof process !== "undefined" && process.browser) return false;
	const protocol = getProtocol(path);
	return protocol === void 0 || protocol === "file";
}
/**
* Converts a filesystem path to a properly-encoded URL.
*
* This is intended to handle situations where JSON Schema $Ref Parser is called
* with a filesystem path that contains characters which are not allowed in URLs.
*
* @example
* The following filesystem paths would be converted to the following URLs:
*
*    <"!@#$%^&*+=?'>.json              ==>   %3C%22!@%23$%25%5E&*+=%3F\'%3E.json
*    C:\\My Documents\\File (1).json   ==>   C:/My%20Documents/File%20(1).json
*    file://Project #42/file.json      ==>   file://Project%20%2342/file.json
*
* @param path
* @returns
*/
function fromFileSystemPath(path) {
	if (isWindows()) {
		const projectDir = cwd();
		const upperPath = path.toUpperCase();
		const posixUpper = convertPathToPosix(projectDir).toUpperCase();
		const hasProjectDir = upperPath.includes(posixUpper);
		const hasProjectUri = upperPath.includes(posixUpper);
		const isAbsolutePath = win32.isAbsolute(path) || path.startsWith("http://") || path.startsWith("https://") || path.startsWith("file://");
		if (!(hasProjectDir || hasProjectUri || isAbsolutePath) && !projectDir.startsWith("http")) path = join(projectDir, path);
		path = convertPathToPosix(path);
	}
	path = encodeURI(path);
	for (const pattern of urlEncodePatterns) path = path.replace(pattern[0], pattern[1]);
	return path;
}
/**
* Converts a URL to a local filesystem path.
*/
function toFileSystemPath(path, keepFileProtocol) {
	path = decodeURI(path);
	for (let i = 0; i < urlDecodePatterns.length; i += 2) path = path.replace(urlDecodePatterns[i], urlDecodePatterns[i + 1]);
	let isFileUrl = path.substr(0, 7).toLowerCase() === "file://";
	if (isFileUrl) {
		path = path[7] === "/" ? path.substr(8) : path.substr(7);
		if (isWindows() && path[1] === "/") path = path[0] + ":" + path.substr(1);
		if (keepFileProtocol) path = "file:///" + path;
		else {
			isFileUrl = false;
			path = isWindows() ? path : "/" + path;
		}
	}
	if (isWindows() && !isFileUrl) {
		path = path.replace(forwardSlashPattern, "\\");
		if (path.substr(1, 2) === ":\\") path = path[0].toUpperCase() + path.substr(1);
	}
	return path;
}
//#endregion
//#region src/util/errors.ts
var JSONParserError = class extends Error {
	name;
	message;
	source;
	path;
	code;
	constructor(message, source) {
		super();
		this.code = "EUNKNOWN";
		this.name = "JSONParserError";
		this.message = message;
		this.source = source;
		this.path = null;
		Ono.extend(this);
	}
	get footprint() {
		return `${this.path}+${this.source}+${this.code}+${this.message}`;
	}
};
var JSONParserErrorGroup = class JSONParserErrorGroup extends Error {
	files;
	constructor(parser) {
		super();
		this.files = parser;
		this.name = "JSONParserErrorGroup";
		this.message = `${this.errors.length} error${this.errors.length > 1 ? "s" : ""} occurred while reading '${toFileSystemPath(parser.$refs._root$Ref.path)}'`;
		Ono.extend(this);
	}
	static getParserErrors(parser) {
		const errors = [];
		for (const $ref of Object.values(parser.$refs._$refs)) if ($ref.errors) errors.push(...$ref.errors);
		return errors;
	}
	get errors() {
		return JSONParserErrorGroup.getParserErrors(this.files);
	}
};
var ParserError = class extends JSONParserError {
	code = "EPARSER";
	name = "ParserError";
	constructor(message, source) {
		super(`Error parsing ${source}: ${message}`, source);
	}
};
var UnmatchedParserError = class extends JSONParserError {
	code = "EUNMATCHEDPARSER";
	name = "UnmatchedParserError";
	constructor(source) {
		super(`Could not find parser for "${source}"`, source);
	}
};
var ResolverError = class extends JSONParserError {
	code = "ERESOLVER";
	name = "ResolverError";
	ioErrorCode;
	constructor(ex, source) {
		super(ex.message || `Error reading file "${source}"`, source);
		if ("code" in ex) this.ioErrorCode = String(ex.code);
	}
};
var UnmatchedResolverError = class extends JSONParserError {
	code = "EUNMATCHEDRESOLVER";
	name = "UnmatchedResolverError";
	constructor(source) {
		super(`Could not find resolver for "${source}"`, source);
	}
};
var MissingPointerError = class extends JSONParserError {
	code = "EMISSINGPOINTER";
	name = "MissingPointerError";
	constructor(token, path) {
		super(`Missing $ref pointer "${getHash(path)}". Token "${token}" does not exist.`, stripHash(path));
	}
};
var TimeoutError = class extends JSONParserError {
	code = "ETIMEOUT";
	name = "TimeoutError";
	constructor(timeout) {
		super(`Dereferencing timeout reached: ${timeout}ms`);
	}
};
var InvalidPointerError = class extends JSONParserError {
	code = "EUNMATCHEDRESOLVER";
	name = "InvalidPointerError";
	constructor(pointer, path) {
		super(`Invalid $ref pointer "${pointer}". Pointers must begin with "#/"`, stripHash(path));
	}
};
function isHandledError(err) {
	return err instanceof JSONParserError || err instanceof JSONParserErrorGroup;
}
function normalizeError(err) {
	if (err.path === null) err.path = [];
	return err;
}
//#endregion
//#region src/ref.ts
/**
* This class represents a single JSON reference and its resolved value.
*
* @class
*/
var $Ref = class $Ref {
	/**
	* The file path or URL of the referenced file.
	* This path is relative to the path of the main JSON schema file.
	*
	* This path does NOT contain document fragments (JSON pointers). It always references an ENTIRE file.
	* Use methods such as {@link $Ref#get}, {@link $Ref#resolve}, and {@link $Ref#exists} to get
	* specific JSON pointers within the file.
	*
	* @type {string}
	*/
	path;
	/**
	* The resolved value of the JSON reference.
	* Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
	*
	* @type {?*}
	*/
	value;
	/**
	* The {@link $Refs} object that contains this {@link $Ref} object.
	*
	* @type {$Refs}
	*/
	$refs;
	/**
	* Indicates the type of {@link $Ref#path} (e.g., "file", "http", etc.)
	*/
	pathType;
	/**
	* List of all errors. Undefined if no errors.
	*/
	errors = [];
	constructor($refs) {
		this.$refs = $refs;
	}
	/**
	* Pushes an error to errors array.
	*
	* @param err - The error to be pushed
	* @returns
	*/
	addError(err) {
		if (this.errors === void 0) this.errors = [];
		const existingErrors = this.errors.map(({ footprint }) => footprint);
		if ("errors" in err && Array.isArray(err.errors)) this.errors.push(...err.errors.map(normalizeError).filter(({ footprint }) => !existingErrors.includes(footprint)));
		else if (!("footprint" in err) || !existingErrors.includes(err.footprint)) this.errors.push(normalizeError(err));
	}
	/**
	* Determines whether the given JSON reference exists within this {@link $Ref#value}.
	*
	* @param path - The full path being resolved, optionally with a JSON pointer in the hash
	* @param options
	* @returns
	*/
	exists(path, options) {
		try {
			this.resolve(path, options);
			return true;
		} catch {
			return false;
		}
	}
	/**
	* Resolves the given JSON reference within this {@link $Ref#value} and returns the resolved value.
	*
	* @param path - The full path being resolved, optionally with a JSON pointer in the hash
	* @param options
	* @returns - Returns the resolved value
	*/
	get(path, options) {
		return this.resolve(path, options)?.value;
	}
	/**
	* Resolves the given JSON reference within this {@link $Ref#value}.
	*
	* @param path - The full path being resolved, optionally with a JSON pointer in the hash
	* @param options
	* @param friendlyPath - The original user-specified path (used for error messages)
	* @param pathFromRoot - The path of `obj` from the schema root
	* @returns
	*/
	resolve(path, options, friendlyPath, pathFromRoot) {
		return new Pointer(this, path, friendlyPath).resolve(this.value, options, pathFromRoot);
	}
	/**
	* Sets the value of a nested property within this {@link $Ref#value}.
	* If the property, or any of its parents don't exist, they will be created.
	*
	* @param path - The full path of the property to set, optionally with a JSON pointer in the hash
	* @param value - The value to assign
	*/
	set(path, value) {
		const pointer = new Pointer(this, path);
		this.value = pointer.set(this.value, value);
	}
	/**
	* Determines whether the given value is a JSON reference.
	*
	* @param value - The value to inspect
	* @returns
	*/
	static is$Ref(value) {
		return Boolean(value) && typeof value === "object" && value !== null && "$ref" in value && typeof value.$ref === "string" && Boolean(value.$ref.length);
	}
	/**
	* Determines whether the given value is an external JSON reference.
	*
	* @param value - The value to inspect
	* @returns
	*/
	static isExternal$Ref(value) {
		return $Ref.is$Ref(value) && value.$ref[0] !== "#";
	}
	/**
	* Determines whether the given value is a JSON reference, and whether it is allowed by the options.
	*
	* @param value - The value to inspect
	* @param options
	* @returns
	*/
	static isAllowed$Ref(value) {
		if (this.is$Ref(value)) {
			if (value.$ref.substring(0, 2) === "#/" || value.$ref === "#") return true;
			else if (value.$ref[0] !== "#") return true;
		}
	}
	/**
	* Determines whether the given value is a JSON reference that "extends" its resolved value.
	* That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to
	* an existing value, this $ref actually creates a NEW value that is a shallow copy of the resolved
	* value, plus the extra properties.
	*
	* @example: {
	person: {
	properties: {
	firstName: { type: string }
	lastName: { type: string }
	}
	}
	employee: {
	properties: {
	$ref: #/person/properties
	salary: { type: number }
	}
	}
	}
	*  In this example, "employee" is an extended $ref, since it extends "person" with an additional
	*  property (salary).  The result is a NEW value that looks like this:
	*
	*  {
	*    properties: {
	*      firstName: { type: string }
	*      lastName: { type: string }
	*      salary: { type: number }
	*    }
	*  }
	*
	* @param value - The value to inspect
	* @returns
	*/
	static isExtended$Ref(value) {
		return $Ref.is$Ref(value) && Object.keys(value).length > 1;
	}
	/**
	* Returns the resolved value of a JSON Reference.
	* If necessary, the resolved value is merged with the JSON Reference to create a new object
	*
	* @example: {
	person: {
	properties: {
	firstName: { type: string }
	lastName: { type: string }
	}
	}
	employee: {
	properties: {
	$ref: #/person/properties
	salary: { type: number }
	}
	}
	} When "person" and "employee" are merged, you end up with the following object:
	*
	*  {
	*    properties: {
	*      firstName: { type: string }
	*      lastName: { type: string }
	*      salary: { type: number }
	*    }
	*  }
	*
	* @param $ref - The JSON reference object (the one with the "$ref" property)
	* @param resolvedValue - The resolved value, which can be any type
	* @returns - Returns the dereferenced value
	*/
	static dereference($ref, resolvedValue) {
		if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
			const merged = {};
			for (const key of Object.keys($ref)) if (key !== "$ref") merged[key] = $ref[key];
			for (const key of Object.keys(resolvedValue)) if (!(key in merged)) merged[key] = resolvedValue[key];
			return merged;
		} else return resolvedValue;
	}
};
//#endregion
//#region src/pointer.ts
const slashes = /\//g;
const tildes = /~/g;
const escapedSlash = /~1/g;
const escapedTilde = /~0/g;
const safeDecodeURIComponent = (encodedURIComponent) => {
	try {
		return decodeURIComponent(encodedURIComponent);
	} catch {
		return encodedURIComponent;
	}
};
/**
* This class represents a single JSON pointer and its resolved value.
*
* @param $ref
* @param path
* @param [friendlyPath] - The original user-specified path (used for error messages)
* @class
*/
var Pointer = class Pointer {
	/**
	* The {@link $Ref} object that contains this {@link Pointer} object.
	*/
	$ref;
	/**
	* The file path or URL, containing the JSON pointer in the hash.
	* This path is relative to the path of the main JSON schema file.
	*/
	path;
	/**
	* The original path or URL, used for error messages.
	*/
	originalPath;
	/**
	* The value of the JSON pointer.
	* Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
	*/
	value;
	/**
	* Indicates whether the pointer references itself.
	*/
	circular;
	/**
	* The number of indirect references that were traversed to resolve the value.
	* Resolving a single pointer may require resolving multiple $Refs.
	*/
	indirections;
	constructor($ref, path, friendlyPath) {
		this.$ref = $ref;
		this.path = path;
		this.originalPath = friendlyPath || path;
		this.value = void 0;
		this.circular = false;
		this.indirections = 0;
	}
	/**
	* Resolves the value of a nested property within the given object.
	*
	* @param obj - The object that will be crawled
	* @param options
	* @param pathFromRoot - the path of place that initiated resolving
	*
	* @returns
	* Returns a JSON pointer whose {@link Pointer#value} is the resolved value.
	* If resolving this value required resolving other JSON references, then
	* the {@link Pointer#$ref} and {@link Pointer#path} will reflect the resolution path
	* of the resolved value.
	*/
	resolve(obj, options, pathFromRoot) {
		const tokens = Pointer.parse(this.path, this.originalPath);
		this.value = unwrapOrThrow(obj);
		const errors = [];
		for (let i = 0; i < tokens.length; i++) {
			if (resolveIf$Ref(this, options, pathFromRoot)) this.path = Pointer.join(this.path, tokens.slice(i));
			if (typeof this.value === "object" && this.value !== null && !isRootPath(pathFromRoot) && "$ref" in this.value) return this;
			const token = tokens[i];
			if (this.value[token] === void 0 || this.value[token] === null && i === tokens.length - 1) {
				let didFindSubstringSlashMatch = false;
				for (let j = tokens.length - 1; j > i; j--) {
					const joinedToken = tokens.slice(i, j + 1).join("/");
					if (this.value[joinedToken] !== void 0) {
						this.value = this.value[joinedToken];
						i = j;
						didFindSubstringSlashMatch = true;
						break;
					}
				}
				if (didFindSubstringSlashMatch) continue;
				this.value = null;
				errors.push(new MissingPointerError(token, decodeURI(this.originalPath)));
			} else this.value = this.value[token];
		}
		if (errors.length) throw errors.length === 1 ? errors[0] : new AggregateError(errors, "Multiple missing pointer errors");
		if (!this.value || this.value.$ref && resolve(this.path, this.value.$ref) !== pathFromRoot) resolveIf$Ref(this, options, pathFromRoot);
		return this;
	}
	/**
	* Sets the value of a nested property within the given object.
	*
	* @param obj - The object that will be crawled
	* @param value - the value to assign
	* @param options
	*
	* @returns
	* Returns the modified object, or an entirely new object if the entire object is overwritten.
	*/
	set(obj, value, options) {
		const tokens = Pointer.parse(this.path);
		let token;
		if (!tokens.length) {
			this.value = value;
			return value;
		}
		this.value = unwrapOrThrow(obj);
		for (let i = 0; i < tokens.length - 1; i++) {
			resolveIf$Ref(this, options);
			token = tokens[i];
			if (this.value && this.value[token] !== void 0) this.value = this.value[token];
			else this.value = setValue(this, token, {});
		}
		resolveIf$Ref(this, options);
		token = tokens[tokens.length - 1];
		setValue(this, token, value);
		return obj;
	}
	/**
	* Parses a JSON pointer (or a path containing a JSON pointer in the hash)
	* and returns an array of the pointer's tokens.
	* (e.g., "schema.json#/definitions/person/name" => ["definitions", "person", "name"])
	*
	* The pointer is parsed according to RFC 6901
	* {@link https://tools.ietf.org/html/rfc6901#section-3}
	*
	* @param path
	* @param [originalPath]
	* @returns
	*/
	static parse(path, originalPath) {
		const pointer = getHash(path).substring(1);
		if (!pointer) return [];
		const split = pointer.split("/");
		for (let i = 0; i < split.length; i++) split[i] = safeDecodeURIComponent(split[i].replace(escapedSlash, "/").replace(escapedTilde, "~"));
		if (split[0] !== "") throw new InvalidPointerError(pointer, originalPath === void 0 ? path : originalPath);
		return split.slice(1);
	}
	/**
	* Creates a JSON pointer path, by joining one or more tokens to a base path.
	*
	* @param base - The base path (e.g., "schema.json#/definitions/person")
	* @param tokens - The token(s) to append (e.g., ["name", "first"])
	* @returns
	*/
	static join(base, tokens) {
		if (base.indexOf("#") === -1) base += "#";
		tokens = Array.isArray(tokens) ? tokens : [tokens];
		for (let i = 0; i < tokens.length; i++) {
			const token = tokens[i];
			base += "/" + encodeURIComponent(token.replace(tildes, "~0").replace(slashes, "~1"));
		}
		return base;
	}
};
/**
* If the given pointer's {@link Pointer#value} is a JSON reference,
* then the reference is resolved and {@link Pointer#value} is replaced with the resolved value.
* In addition, {@link Pointer#path} and {@link Pointer#$ref} are updated to reflect the
* resolution path of the new value.
*
* @param pointer
* @param options
* @param [pathFromRoot] - the path of place that initiated resolving
* @returns - Returns `true` if the resolution path changed
*/
function resolveIf$Ref(pointer, options, pathFromRoot) {
	if ($Ref.isAllowed$Ref(pointer.value)) {
		const $refPath = resolve(pointer.path, pointer.value.$ref);
		if ($refPath === pointer.path && !isRootPath(pathFromRoot)) pointer.circular = true;
		else {
			const resolved = pointer.$ref.$refs._resolve($refPath, pointer.path, options);
			if (resolved === null) return false;
			pointer.indirections += resolved.indirections + 1;
			if ($Ref.isExtended$Ref(pointer.value)) {
				pointer.value = $Ref.dereference(pointer.value, resolved.value);
				return false;
			} else {
				pointer.$ref = resolved.$ref;
				pointer.path = resolved.path;
				pointer.value = resolved.value;
			}
			return true;
		}
	}
}
/**
* Sets the specified token value of the {@link Pointer#value}.
*
* The token is evaluated according to RFC 6901.
* {@link https://tools.ietf.org/html/rfc6901#section-4}
*
* @param pointer - The JSON Pointer whose value will be modified
* @param token - A JSON Pointer token that indicates how to modify `obj`
* @param value - The value to assign
* @returns - Returns the assigned value
*/
function setValue(pointer, token, value) {
	if (pointer.value && typeof pointer.value === "object") if (token === "-" && Array.isArray(pointer.value)) pointer.value.push(value);
	else pointer.value[token] = value;
	else throw new JSONParserError(`Error assigning $ref pointer "${pointer.path}". \nCannot set "${token}" of a non-object.`);
	return value;
}
function unwrapOrThrow(value) {
	if (isHandledError(value)) throw value;
	return value;
}
function isRootPath(pathFromRoot) {
	return typeof pathFromRoot == "string" && Pointer.parse(pathFromRoot).length == 0;
}
//#endregion
//#region src/bundle.ts
/**
* Fast lookup using Map instead of linear search with deep equality
*/
const createInventoryLookup = () => {
	const lookup = /* @__PURE__ */ new Map();
	const objectIds = /* @__PURE__ */ new WeakMap();
	let idCounter = 0;
	const getObjectId = (obj) => {
		if (!objectIds.has(obj)) objectIds.set(obj, `obj_${++idCounter}`);
		return objectIds.get(obj);
	};
	const createInventoryKey = ($refParent, $refKey) => `${getObjectId($refParent)}_${$refKey}`;
	return {
		add: (entry) => {
			const key = createInventoryKey(entry.parent, entry.key);
			lookup.set(key, entry);
		},
		find: ($refParent, $refKey) => {
			const key = createInventoryKey($refParent, $refKey);
			return lookup.get(key);
		},
		remove: (entry) => {
			const key = createInventoryKey(entry.parent, entry.key);
			lookup.delete(key);
		}
	};
};
/**
* Determine the container type from a JSON Pointer path.
* Analyzes the path tokens to identify the appropriate OpenAPI component container.
*
* @param path - The JSON Pointer path to analyze
* @returns The container type: "schemas", "parameters", "requestBodies", "responses", or "headers"
*/
const getContainerTypeFromPath = (path) => {
	const tokens = Pointer.parse(path);
	const has = (t) => tokens.includes(t);
	if (has("parameters")) return "parameters";
	if (has("requestBody")) return "requestBodies";
	if (has("headers")) return "headers";
	if (has("responses")) return "responses";
	if (has("schema")) return "schemas";
	return "schemas";
};
/**
* Inventories the given JSON Reference (i.e., records detailed information about it so we can
* optimize all $refs in the schema), and then crawls the resolved value.
*/
const inventory$Ref = ({ $refKey, $refParent, $refs, indirections, inventory, inventoryLookup, options, path, pathFromRoot, resolvedRefs = /* @__PURE__ */ new Map(), visitedObjects = /* @__PURE__ */ new WeakSet() }) => {
	const $ref = $refKey === null ? $refParent : $refParent[$refKey];
	const $refPath = resolve(path, $ref.$ref);
	let pointer = resolvedRefs.get($refPath);
	if (!pointer) {
		try {
			pointer = $refs._resolve($refPath, pathFromRoot, options);
		} catch (error) {
			if (error instanceof MissingPointerError) {
				const hash = getHash($refPath);
				if (hash) {
					const baseFile = stripHash($refPath);
					for (const filePath of Object.keys($refs._$refs)) {
						if (filePath === baseFile) continue;
						try {
							pointer = $refs._resolve(filePath + hash, pathFromRoot, options);
							if (pointer) break;
						} catch {}
					}
				}
				if (!pointer) {
					console.warn(`Skipping unresolvable $ref: ${$refPath}`);
					return;
				}
			} else throw error;
		}
		if (pointer) resolvedRefs.set($refPath, pointer);
	}
	if (pointer === null) return;
	const depth = Pointer.parse(pathFromRoot).length;
	const file = stripHash(pointer.path);
	const hash = getHash(pointer.path);
	const external = file !== $refs._root$Ref.path;
	const extended = $Ref.isExtended$Ref($ref);
	indirections += pointer.indirections;
	const existingEntry = inventoryLookup.find($refParent, $refKey);
	if (existingEntry && existingEntry.pathFromRoot === pathFromRoot) if (depth < existingEntry.depth || indirections < existingEntry.indirections) {
		removeFromInventory(inventory, existingEntry);
		inventoryLookup.remove(existingEntry);
	} else return;
	const newEntry = {
		$ref,
		circular: pointer.circular,
		depth,
		extended,
		external,
		file,
		hash,
		indirections,
		key: $refKey,
		originalContainerType: external ? getContainerTypeFromPath(pointer.path) : void 0,
		parent: $refParent,
		pathFromRoot,
		value: pointer.value
	};
	inventory.push(newEntry);
	inventoryLookup.add(newEntry);
	if (!existingEntry || external) {
		let crawlPath = pointer.path;
		if (file !== stripHash($refPath)) crawlPath = file + getHash(pointer.path);
		crawl$1({
			$refs,
			indirections: indirections + 1,
			inventory,
			inventoryLookup,
			key: null,
			options,
			parent: pointer.value,
			path: crawlPath,
			pathFromRoot,
			resolvedRefs,
			visitedObjects
		});
	}
};
/**
* Recursively crawls the given value, and inventories all JSON references.
*/
const crawl$1 = ({ $refs, indirections, inventory, inventoryLookup, key, options, parent, path, pathFromRoot, resolvedRefs = /* @__PURE__ */ new Map(), visitedObjects = /* @__PURE__ */ new WeakSet() }) => {
	const obj = key === null ? parent : parent[key];
	if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
		if (visitedObjects.has(obj)) return;
		if ($Ref.isAllowed$Ref(obj)) inventory$Ref({
			$refKey: key,
			$refParent: parent,
			$refs,
			indirections,
			inventory,
			inventoryLookup,
			options,
			path,
			pathFromRoot,
			resolvedRefs,
			visitedObjects
		});
		else {
			visitedObjects.add(obj);
			const keys = Object.keys(obj).sort((a, b) => {
				if (a === "definitions") return -1;
				else if (b === "definitions") return 1;
				else return a.length - b.length;
			});
			for (const key of keys) {
				const keyPath = Pointer.join(path, key);
				const keyPathFromRoot = Pointer.join(pathFromRoot, key);
				const value = obj[key];
				if ($Ref.isAllowed$Ref(value)) inventory$Ref({
					$refKey: key,
					$refParent: obj,
					$refs,
					indirections,
					inventory,
					inventoryLookup,
					options,
					path,
					pathFromRoot: keyPathFromRoot,
					resolvedRefs,
					visitedObjects
				});
				else crawl$1({
					$refs,
					indirections,
					inventory,
					inventoryLookup,
					key,
					options,
					parent: obj,
					path: keyPath,
					pathFromRoot: keyPathFromRoot,
					resolvedRefs,
					visitedObjects
				});
			}
		}
	}
};
/**
* Remap external refs by hoisting resolved values into a shared container in the root schema
* and pointing all occurrences to those internal definitions. Internal refs remain internal.
*/
function remap(parser, inventory) {
	const root = parser.schema;
	inventory.sort((a, b) => {
		if (a.file !== b.file) return a.file < b.file ? -1 : 1;
		else if (a.hash !== b.hash) return a.hash < b.hash ? -1 : 1;
		else if (a.circular !== b.circular) return a.circular ? -1 : 1;
		else if (a.extended !== b.extended) return a.extended ? 1 : -1;
		else if (a.indirections !== b.indirections) return a.indirections - b.indirections;
		else if (a.depth !== b.depth) return a.depth - b.depth;
		else {
			const aDefinitionsIndex = a.pathFromRoot.lastIndexOf("/definitions");
			const bDefinitionsIndex = b.pathFromRoot.lastIndexOf("/definitions");
			if (aDefinitionsIndex !== bDefinitionsIndex) return bDefinitionsIndex - aDefinitionsIndex;
			else return a.pathFromRoot.length - b.pathFromRoot.length;
		}
	});
	const ensureContainer = (type) => {
		const isOas3 = Boolean(root && typeof root === "object" && typeof root.openapi === "string");
		const isOas2 = Boolean(root && typeof root === "object" && typeof root.swagger === "string");
		if (isOas3) {
			if (!root.components || typeof root.components !== "object") root.components = {};
			if (!root.components[type] || typeof root.components[type] !== "object") root.components[type] = {};
			return {
				obj: root.components[type],
				prefix: `#/components/${type}`
			};
		}
		if (isOas2) {
			if (type === "schemas") {
				if (!root.definitions || typeof root.definitions !== "object") root.definitions = {};
				return {
					obj: root.definitions,
					prefix: "#/definitions"
				};
			}
			if (type === "parameters") {
				if (!root.parameters || typeof root.parameters !== "object") root.parameters = {};
				return {
					obj: root.parameters,
					prefix: "#/parameters"
				};
			}
			if (type === "responses") {
				if (!root.responses || typeof root.responses !== "object") root.responses = {};
				return {
					obj: root.responses,
					prefix: "#/responses"
				};
			}
			if (!root.definitions || typeof root.definitions !== "object") root.definitions = {};
			return {
				obj: root.definitions,
				prefix: "#/definitions"
			};
		}
		if (root && typeof root === "object") {
			if (root.components && typeof root.components === "object") {
				if (!root.components[type] || typeof root.components[type] !== "object") root.components[type] = {};
				return {
					obj: root.components[type],
					prefix: `#/components/${type}`
				};
			}
			if (root.definitions && typeof root.definitions === "object") return {
				obj: root.definitions,
				prefix: "#/definitions"
			};
			if (!root.components || typeof root.components !== "object") root.components = {};
			if (!root.components[type] || typeof root.components[type] !== "object") root.components[type] = {};
			return {
				obj: root.components[type],
				prefix: `#/components/${type}`
			};
		}
		root.definitions = root.definitions || {};
		return {
			obj: root.definitions,
			prefix: "#/definitions"
		};
	};
	/**
	* Choose the appropriate component container for bundling.
	* Prioritizes the original container type from external files over usage location.
	*
	* @param entry - The inventory entry containing reference information
	* @returns The container type to use for bundling
	*/
	const chooseComponent = (entry) => {
		if (entry.originalContainerType) return entry.originalContainerType;
		return getContainerTypeFromPath(entry.pathFromRoot);
	};
	const targetToNameByPrefix = /* @__PURE__ */ new Map();
	const usedNamesByObj = /* @__PURE__ */ new Map();
	const sanitize = (name) => name.replace(/[^A-Za-z0-9_-]/g, "_");
	const baseName = (filePath) => {
		try {
			const parts = filePath.split("#")[0].split("/");
			const filename = parts[parts.length - 1] || "schema";
			const dot = filename.lastIndexOf(".");
			return sanitize(dot > 0 ? filename.substring(0, dot) : filename);
		} catch {
			return "schema";
		}
	};
	const lastToken = (hash) => {
		if (!hash || hash === "#") return "root";
		const tokens = hash.replace(/^#\//, "").split("/");
		return sanitize(tokens[tokens.length - 1] || "root");
	};
	const uniqueName = (containerObj, proposed) => {
		if (!usedNamesByObj.has(containerObj)) usedNamesByObj.set(containerObj, new Set(Object.keys(containerObj || {})));
		const used = usedNamesByObj.get(containerObj);
		let name = proposed;
		let i = 2;
		while (used.has(name)) name = `${proposed}_${i++}`;
		used.add(name);
		return name;
	};
	for (const entry of inventory) {
		if (!entry || !entry.$ref || typeof entry.$ref !== "object") continue;
		if (!entry.external) {
			if (!entry.extended && entry.$ref && typeof entry.$ref === "object") entry.$ref.$ref = decodeURI(entry.hash);
			continue;
		}
		if (entry.circular) {
			if (entry.$ref && typeof entry.$ref === "object") entry.$ref.$ref = decodeURI(entry.pathFromRoot);
			continue;
		}
		const { obj: container, prefix } = ensureContainer(chooseComponent(entry));
		const targetKey = `${entry.file}::${entry.hash}`;
		if (!targetToNameByPrefix.has(prefix)) targetToNameByPrefix.set(prefix, /* @__PURE__ */ new Map());
		const namesForPrefix = targetToNameByPrefix.get(prefix);
		let defName = namesForPrefix.get(targetKey);
		if (!defName) {
			let proposedBase = baseName(entry.file);
			try {
				const parserAny = parser;
				if (parserAny && parserAny.sourcePathToPrefix && typeof parserAny.sourcePathToPrefix.get === "function") {
					const withoutHash = (entry.file || "").split("#")[0];
					const mapped = parserAny.sourcePathToPrefix.get(withoutHash);
					if (mapped && typeof mapped === "string") proposedBase = mapped;
				}
			} catch {}
			const isWholeFileRef = !entry.hash || entry.hash === "#";
			const schemaName = isWholeFileRef ? proposedBase : lastToken(entry.hash);
			let proposed = schemaName;
			if (!usedNamesByObj.has(container)) usedNamesByObj.set(container, new Set(Object.keys(container || {})));
			if (usedNamesByObj.get(container).has(proposed)) proposed = isWholeFileRef ? proposedBase : `${proposedBase}_${schemaName}`;
			defName = uniqueName(container, proposed);
			namesForPrefix.set(targetKey, defName);
			container[defName] = entry.value;
		}
		const refPath = `${prefix}/${defName}`;
		if (entry.extended && entry.$ref && typeof entry.$ref === "object") entry.$ref.$ref = refPath;
		else entry.parent[entry.key] = { $ref: refPath };
	}
}
function removeFromInventory(inventory, entry) {
	const index = inventory.indexOf(entry);
	inventory.splice(index, 1);
}
/**
* Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
* only has *internal* references, not any *external* references.
* This method mutates the JSON schema object, adding new references and re-mapping existing ones.
*
* @param parser
* @param options
*/
function bundle(parser, options) {
	const inventory = [];
	const inventoryLookup = createInventoryLookup();
	const visitedObjects = /* @__PURE__ */ new WeakSet();
	const resolvedRefs = /* @__PURE__ */ new Map();
	crawl$1({
		$refs: parser.$refs,
		indirections: 0,
		inventory,
		inventoryLookup,
		key: "schema",
		options,
		parent: parser,
		path: parser.$refs._root$Ref.path + "#",
		pathFromRoot: "#",
		resolvedRefs,
		visitedObjects
	});
	remap(parser, inventory);
}
//#endregion
//#region src/parsers/binary.ts
const BINARY_REGEXP = /\.(jpeg|jpg|gif|png|bmp|ico)$/i;
const binaryParser = {
	canHandle: (file) => Buffer.isBuffer(file.data) && BINARY_REGEXP.test(file.url),
	handler: (file) => Buffer.isBuffer(file.data) ? file.data : Buffer.from(file.data),
	name: "binary"
};
//#endregion
//#region src/parsers/json.ts
const jsonParser = {
	canHandle: (file) => file.extension === ".json",
	async handler(file) {
		let data = file.data;
		if (Buffer.isBuffer(data)) data = data.toString();
		if (typeof data !== "string") return data;
		if (!data.trim().length) return;
		try {
			return JSON.parse(data);
		} catch (error) {
			try {
				const firstCurlyBrace = data.indexOf("{");
				data = data.slice(firstCurlyBrace);
				return JSON.parse(data);
			} catch (error) {
				throw new ParserError(error.message, file.url);
			}
		}
	},
	name: "json"
};
//#endregion
//#region src/parsers/text.ts
const TEXT_REGEXP = /\.(txt|htm|html|md|xml|js|min|map|css|scss|less|svg)$/i;
const textParser = {
	canHandle: (file) => (typeof file.data === "string" || Buffer.isBuffer(file.data)) && TEXT_REGEXP.test(file.url),
	handler(file) {
		if (typeof file.data === "string") return file.data;
		if (!Buffer.isBuffer(file.data)) throw new ParserError("data is not text", file.url);
		return file.data.toString("utf-8");
	},
	name: "text"
};
//#endregion
//#region src/parsers/yaml.ts
const yamlParser = {
	canHandle: (file) => [
		".yaml",
		".yml",
		".json"
	].includes(file.extension),
	handler: async (file) => {
		const data = Buffer.isBuffer(file.data) ? file.data.toString() : file.data;
		if (typeof data !== "string") return data;
		try {
			return load(data);
		} catch (error) {
			throw new ParserError(error?.message || "Parser Error", file.url);
		}
	},
	name: "yaml"
};
//#endregion
//#region src/options.ts
const getJsonSchemaRefParserDefaultOptions = () => ({
	/**
	* Determines the types of JSON references that are allowed.
	*/
	dereference: {
		/**
		* Dereference circular (recursive) JSON references?
		* If false, then a {@link ReferenceError} will be thrown if a circular reference is found.
		* If "ignore", then circular references will not be dereferenced.
		*
		* @type {boolean|string}
		*/
		circular: true,
		/**
		* A function, called for each path, which can return true to stop this path and all
		* subpaths from being dereferenced further. This is useful in schemas where some
		* subpaths contain literal $ref keys that should not be dereferenced.
		*
		* @type {function}
		*/
		excludedPathMatcher: () => false,
		referenceResolution: "relative"
	},
	/**
	* Determines how different types of files will be parsed.
	*
	* You can add additional parsers of your own, replace an existing one with
	* your own implementation, or disable any parser by setting it to false.
	*/
	parse: {
		binary: { ...binaryParser },
		json: { ...jsonParser },
		text: { ...textParser },
		yaml: { ...yamlParser }
	}
});
//#endregion
//#region src/util/plugins.ts
/**
* Runs the specified method of the given plugins, in order, until one of them returns a successful result.
* Each method can return a synchronous value, a Promise, or call an error-first callback.
* If the promise resolves successfully, or the callback is called without an error, then the result
* is immediately returned and no further plugins are called.
* If the promise rejects, or the callback is called with an error, then the next plugin is called.
* If ALL plugins fail, then the last error is thrown.
*/
async function run(plugins, file) {
	let index = 0;
	let lastError;
	let plugin;
	return new Promise((resolve, reject) => {
		const runNextPlugin = async () => {
			plugin = plugins[index++];
			if (!plugin) return reject(lastError);
			try {
				const result = await plugin.handler(file);
				if (result !== void 0) return resolve({
					plugin,
					result
				});
				if (index === plugins.length) throw new Error("No promise has been returned.");
			} catch (error) {
				lastError = {
					error,
					plugin
				};
				runNextPlugin();
			}
		};
		runNextPlugin();
	});
}
//#endregion
//#region src/parse.ts
/**
* Prepares the file object so we can populate it with data and other values
* when it's read and parsed. This "file object" will be passed to all
* resolvers and parsers.
*/
function newFile(path) {
	let url = path;
	const hashIndex = url.indexOf("#");
	let hash = "";
	if (hashIndex > -1) {
		hash = url.substring(hashIndex);
		url = url.substring(0, hashIndex);
	}
	return {
		extension: getExtension(url),
		hash,
		url
	};
}
/**
* Parses the given file's contents, using the configured parser plugins.
*/
async function parseFile(file, options) {
	try {
		const parsers = [
			options.json,
			options.yaml,
			options.text,
			options.binary
		];
		const filtered = parsers.filter((plugin) => plugin.canHandle(file));
		return await run(filtered.length ? filtered : parsers, file);
	} catch (error) {
		if (error && error.message && error.message.startsWith("Error parsing")) throw error;
		if (!error || !("error" in error)) throw ono.syntax(`Unable to parse ${file.url}`);
		if (error.error instanceof ParserError) throw error.error;
		throw new ParserError(error.error.message, file.url);
	}
}
//#endregion
//#region src/refs.ts
/**
* When you call the resolve method, the value that gets passed to the callback function (or Promise) is a $Refs object. This same object is accessible via the parser.$refs property of $RefParser objects.
*
* This object is a map of JSON References and their resolved values. It also has several convenient helper methods that make it easy for you to navigate and manipulate the JSON References.
*
* See https://apitools.dev/json-schema-ref-parser/docs/refs.html
*/
var $Refs = class {
	/**
	* This property is true if the schema contains any circular references. You may want to check this property before serializing the dereferenced schema as JSON, since JSON.stringify() does not support circular references by default.
	*
	* See https://apitools.dev/json-schema-ref-parser/docs/refs.html#circular
	*/
	circular;
	/**
	* Returns the paths/URLs of all the files in your schema (including the main schema file).
	*
	* See https://apitools.dev/json-schema-ref-parser/docs/refs.html#pathstypes
	*
	* @param types (optional) Optionally only return certain types of paths ("file", "http", etc.)
	*/
	paths(...types) {
		return getPaths(this._$refs, types.flat()).map((path) => convertPathToPosix(path.decoded));
	}
	/**
	* Returns a map of paths/URLs and their correspond values.
	*
	* See https://apitools.dev/json-schema-ref-parser/docs/refs.html#valuestypes
	*
	* @param types (optional) Optionally only return values from certain locations ("file", "http", etc.)
	*/
	values(...types) {
		const $refs = this._$refs;
		return getPaths($refs, types.flat()).reduce((obj, path) => {
			obj[convertPathToPosix(path.decoded)] = $refs[path.encoded].value;
			return obj;
		}, {});
	}
	/**
	* Returns `true` if the given path exists in the schema; otherwise, returns `false`
	*
	* See https://apitools.dev/json-schema-ref-parser/docs/refs.html#existsref
	*
	* @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash
	*/
	/**
	* Determines whether the given JSON reference exists.
	*
	* @param path - The path being resolved, optionally with a JSON pointer in the hash
	* @param [options]
	* @returns
	*/
	exists(path, options) {
		try {
			this._resolve(path, "", options);
			return true;
		} catch {
			return false;
		}
	}
	/**
	* Resolves the given JSON reference and returns the resolved value.
	*
	* @param path - The path being resolved, with a JSON pointer in the hash
	* @param [options]
	* @returns - Returns the resolved value
	*/
	get(path, options) {
		return this._resolve(path, "", options).value;
	}
	/**
	* Sets the value at the given path in the schema. If the property, or any of its parents, don't exist, they will be created.
	*
	* @param path The JSON Reference path, optionally with a JSON Pointer in the hash
	* @param value The value to assign. Can be anything (object, string, number, etc.)
	*/
	set(path, value) {
		const absPath = resolve(this._root$Ref.path, path);
		const withoutHash = stripHash(absPath);
		const $ref = this._$refs[withoutHash];
		if (!$ref) throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`);
		$ref.set(absPath, value);
	}
	/**
	* Returns the specified {@link $Ref} object, or undefined.
	*
	* @param path - The path being resolved, optionally with a JSON pointer in the hash
	* @returns
	* @protected
	*/
	_get$Ref(path) {
		path = resolve(this._root$Ref.path, path);
		const withoutHash = stripHash(path);
		return this._$refs[withoutHash];
	}
	/**
	* Creates a new {@link $Ref} object and adds it to this {@link $Refs} object.
	*
	* @param path  - The file path or URL of the referenced file
	*/
	_add(path) {
		const withoutHash = stripHash(path);
		const $ref = new $Ref(this);
		$ref.path = withoutHash;
		this._$refs[withoutHash] = $ref;
		this._root$Ref = this._root$Ref || $ref;
		return $ref;
	}
	/**
	* Resolves the given JSON reference.
	*
	* @param path - The path being resolved, optionally with a JSON pointer in the hash
	* @param pathFromRoot - The path of `obj` from the schema root
	* @param [options]
	* @returns
	* @protected
	*/
	_resolve(path, pathFromRoot, options) {
		const absPath = resolve(this._root$Ref.path, path);
		const withoutHash = stripHash(absPath);
		const $ref = this._$refs[withoutHash];
		if (!$ref) throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`);
		if ($ref.value === void 0) {
			console.warn(`$ref entry exists but value is undefined: ${withoutHash}`);
			return null;
		}
		return $ref.resolve(absPath, options, path, pathFromRoot);
	}
	/**
	* A map of paths/urls to {@link $Ref} objects
	*
	* @type {object}
	* @protected
	*/
	_$refs = {};
	/**
	* The {@link $Ref} object that is the root of the JSON schema.
	*
	* @type {$Ref}
	* @protected
	*/
	_root$Ref;
	constructor() {
		/**
		* Indicates whether the schema contains any circular references.
		*
		* @type {boolean}
		*/
		this.circular = false;
		this._$refs = {};
		this._root$Ref = null;
	}
	/**
	* Returns the paths of all the files/URLs that are referenced by the JSON schema,
	* including the schema itself.
	*
	* @param [types] - Only return paths of the given types ("file", "http", etc.)
	* @returns
	*/
	/**
	* Returns the map of JSON references and their resolved values.
	*
	* @param [types] - Only return references of the given types ("file", "http", etc.)
	* @returns
	*/
	/**
	* Returns a POJO (plain old JavaScript object) for serialization as JSON.
	*
	* @returns {object}
	*/
	toJSON = this.values;
};
/**
* Returns the encoded and decoded paths keys of the given object.
*
* @param $refs - The object whose keys are URL-encoded paths
* @param [types] - Only return paths of the given types ("file", "http", etc.)
* @returns
*/
function getPaths($refs, types) {
	let paths = Object.keys($refs);
	types = Array.isArray(types[0]) ? types[0] : Array.prototype.slice.call(types);
	if (types.length && types[0]) paths = paths.filter((key) => types.includes($refs[key].pathType));
	return paths.map((path) => ({
		decoded: $refs[path].pathType === "file" ? toFileSystemPath(path, true) : path,
		encoded: path
	}));
}
//#endregion
//#region src/resolvers/file.ts
const fileResolver = { handler: async ({ file }) => {
	let path;
	try {
		path = toFileSystemPath(file.url);
	} catch (error) {
		throw new ResolverError(ono.uri(error, `Malformed URI: ${file.url}`), file.url);
	}
	try {
		file.data = await fs.promises.readFile(path);
	} catch (error) {
		throw new ResolverError(ono(error, `Error opening file "${path}"`), path);
	}
} };
//#endregion
//#region src/resolvers/url.ts
const sendRequest = async ({ fetchOptions, redirects = [], timeout = 6e4, url }) => {
	url = new URL(url);
	redirects.push(url.href);
	const controller = new AbortController();
	const timeoutId = setTimeout(() => {
		controller.abort();
	}, timeout);
	const response = await fetch(url, {
		signal: controller.signal,
		...fetchOptions
	});
	clearTimeout(timeoutId);
	if (response.status >= 300 && response.status <= 399) {
		if (redirects.length > 5) throw new ResolverError(ono({ status: response.status }, `Error requesting ${redirects[0]}. \nToo many redirects: \n  ${redirects.join(" \n  ")}`));
		if (!("location" in response.headers) || !response.headers.location) throw ono({ status: response.status }, `HTTP ${response.status} redirect with no location header`);
		return sendRequest({
			fetchOptions,
			redirects,
			timeout,
			url: resolve(url.href, response.headers.location)
		});
	}
	return {
		fetchOptions,
		response
	};
};
const urlResolver = { handler: async ({ arrayBuffer, fetch: _fetch, file }) => {
	let data = arrayBuffer;
	if (!data) try {
		const { fetchOptions, response } = await sendRequest({
			fetchOptions: {
				method: "GET",
				..._fetch
			},
			url: file.url
		});
		if (response.status >= 400) {
			if (response.status !== 405 || fetchOptions?.method !== "HEAD") throw ono({ status: response.status }, `HTTP ERROR ${response.status}`);
		}
		data = response.body ? await response.arrayBuffer() : /* @__PURE__ */ new ArrayBuffer(0);
	} catch (error) {
		throw new ResolverError(ono(error, `Error requesting ${file.url}`), file.url);
	}
	file.data = Buffer.from(data);
} };
//#endregion
//#region src/resolve-external.ts
/**
* Crawls the JSON schema, finds all external JSON references, and resolves their values.
* This method does not mutate the JSON schema. The resolved values are added to {@link $RefParser#$refs}.
*
* NOTE: We only care about EXTERNAL references here. INTERNAL references are only relevant when dereferencing.
*
* @returns
* The promise resolves once all JSON references in the schema have been resolved,
* including nested references that are contained in externally-referenced files.
*/
async function resolveExternal(parser, options) {
	const promises = crawl(parser.schema, {
		$refs: parser.$refs,
		options: options.parse,
		path: `${parser.$refs._root$Ref.path}#`
	});
	await Promise.all(promises);
}
/**
* Recursively crawls the given value, and resolves any external JSON references.
*
* @param obj - The value to crawl. If it's not an object or array, it will be ignored.
* @returns An array of promises. There will be one promise for each JSON reference in `obj`.
* If `obj` does not contain any JSON references, then the array will be empty.
* If any of the JSON references point to files that contain additional JSON references,
* then the corresponding promise will internally reference an array of promises.
*/
function crawl(obj, { $refs, external = false, options, path, seen = /* @__PURE__ */ new Set() }) {
	let promises = [];
	if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) {
		seen.add(obj);
		if ($Ref.isExternal$Ref(obj)) promises.push(resolve$Ref(obj, {
			$refs,
			options,
			path,
			seen
		}));
		for (const [key, value] of Object.entries(obj)) promises = promises.concat(crawl(value, {
			$refs,
			external,
			options,
			path: Pointer.join(path, key),
			seen
		}));
	}
	return promises;
}
/**
* Resolves the given JSON Reference, and then crawls the resulting value.
*
* @param $ref - The JSON Reference to resolve
* @param path - The full path of `$ref`, possibly with a JSON Pointer in the hash
* @param $refs
* @param options
*
* @returns
* The promise resolves once all JSON references in the object have been resolved,
* including nested references that are contained in externally-referenced files.
*/
async function resolve$Ref($ref, { $refs, options, path, seen }) {
	const resolvedPath = resolve(path, $ref.$ref);
	const withoutHash = stripHash(resolvedPath);
	const ref = $refs._$refs[withoutHash];
	if (ref) {
		const promises = crawl(ref.value, {
			$refs,
			external: true,
			options,
			path: `${withoutHash}#`,
			seen
		});
		return Promise.all(promises);
	}
	const file = newFile(resolvedPath);
	const $refAdded = $refs._add(file.url);
	try {
		const resolvedInput = getResolvedInput({ pathOrUrlOrSchema: resolvedPath });
		$refAdded.pathType = resolvedInput.type;
		let promises = [];
		if (resolvedInput.type !== "json") {
			await (resolvedInput.type === "file" ? fileResolver : urlResolver).handler({ file });
			const parseResult = await parseFile(file, options);
			$refAdded.value = parseResult.result;
			promises = crawl(parseResult.result, {
				$refs,
				external: true,
				options,
				path: `${withoutHash}#`,
				seen
			});
		}
		return Promise.all(promises);
	} catch (error) {
		if (isHandledError(error)) $refAdded.value = error;
		throw error;
	}
}
//#endregion
//#region src/index.ts
function getResolvedInput({ pathOrUrlOrSchema }) {
	if (!pathOrUrlOrSchema) throw ono(`Expected a file path, URL, or object. Got ${pathOrUrlOrSchema}`);
	const resolvedInput = {
		path: typeof pathOrUrlOrSchema === "string" ? pathOrUrlOrSchema : "",
		schema: void 0,
		type: "url"
	};
	if (resolvedInput.path && isFileSystemPath(resolvedInput.path)) {
		resolvedInput.path = fromFileSystemPath(resolvedInput.path);
		resolvedInput.type = "file";
	} else if (!resolvedInput.path && pathOrUrlOrSchema && typeof pathOrUrlOrSchema === "object") if ("$id" in pathOrUrlOrSchema && pathOrUrlOrSchema.$id) {
		const { hostname, protocol } = new URL(pathOrUrlOrSchema.$id);
		resolvedInput.path = `${protocol}//${hostname}:${protocol === "https:" ? 443 : 80}`;
		resolvedInput.type = "url";
	} else {
		resolvedInput.schema = pathOrUrlOrSchema;
		resolvedInput.type = "json";
	}
	if (resolvedInput.type !== "json") resolvedInput.path = resolve(cwd(), resolvedInput.path);
	return resolvedInput;
}
/**
* This class parses a JSON schema, builds a map of its JSON references and their resolved values,
* and provides methods for traversing, manipulating, and dereferencing those references.
*/
var $RefParser = class {
	/**
	* The resolved JSON references
	*
	* @type {$Refs}
	* @readonly
	*/
	$refs = new $Refs();
	options = getJsonSchemaRefParserDefaultOptions();
	/**
	* The parsed (and possibly dereferenced) JSON schema object
	*
	* @type {object}
	* @readonly
	*/
	schema = null;
	schemaMany = [];
	schemaManySources = [];
	sourcePathToPrefix = /* @__PURE__ */ new Map();
	/**
	* Bundles all referenced files/URLs into a single schema that only has internal `$ref` pointers. This lets you split-up your schema however you want while you're building it, but easily combine all those files together when it's time to package or distribute the schema to other people. The resulting schema size will be small, since it will still contain internal JSON references rather than being fully-dereferenced.
	*
	* This also eliminates the risk of circular references, so the schema can be safely serialized using `JSON.stringify()`.
	*
	* See https://apitools.dev/json-schema-ref-parser/docs/ref-parser.html#bundleschema-options-callback
	*
	* @param pathOrUrlOrSchema A JSON Schema object, or the file path or URL of a JSON Schema file.
	*/
	async bundle({ arrayBuffer, fetch, pathOrUrlOrSchema, resolvedInput }) {
		await this.parse({
			arrayBuffer,
			fetch,
			pathOrUrlOrSchema,
			resolvedInput
		});
		await resolveExternal(this, this.options);
		if (JSONParserErrorGroup.getParserErrors(this).length) throw new JSONParserErrorGroup(this);
		bundle(this, this.options);
		if (JSONParserErrorGroup.getParserErrors(this).length) throw new JSONParserErrorGroup(this);
		return this.schema;
	}
	/**
	* Bundles multiple roots (files/URLs/objects) into a single schema by creating a synthetic root
	* that references each input, resolving all externals, and then hoisting via the existing bundler.
	*/
	async bundleMany({ arrayBuffer, fetch, pathOrUrlOrSchemas, resolvedInputs }) {
		await this.parseMany({
			arrayBuffer,
			fetch,
			pathOrUrlOrSchemas,
			resolvedInputs
		});
		this.mergeMany();
		await resolveExternal(this, this.options);
		if (JSONParserErrorGroup.getParserErrors(this).length) throw new JSONParserErrorGroup(this);
		bundle(this, this.options);
		if (JSONParserErrorGroup.getParserErrors(this).length) throw new JSONParserErrorGroup(this);
		return this.schema;
	}
	/**
	* Parses the given JSON schema.
	* This method does not resolve any JSON references.
	* It just reads a single file in JSON or YAML format, and parse it as a JavaScript object.
	*
	* @param pathOrUrlOrSchema A JSON Schema object, or the file path or URL of a JSON Schema file.
	* @returns - The returned promise resolves with the parsed JSON schema object.
	*/
	async parse({ arrayBuffer, fetch, pathOrUrlOrSchema, resolvedInput: _resolvedInput }) {
		const resolvedInput = _resolvedInput || getResolvedInput({ pathOrUrlOrSchema });
		const { path, type } = resolvedInput;
		let { schema } = resolvedInput;
		this.schema = null;
		this.$refs = new $Refs();
		if (schema) {
			const $ref = this.$refs._add(path);
			$ref.pathType = isFileSystemPath(path) ? "file" : "http";
			$ref.value = schema;
		} else if (type !== "json") {
			const file = newFile(path);
			const $refAdded = this.$refs._add(file.url);
			$refAdded.pathType = type;
			try {
				await (type === "file" ? fileResolver : urlResolver).handler({
					arrayBuffer,
					fetch,
					file
				});
				const parseResult = await parseFile(file, this.options.parse);
				$refAdded.value = parseResult.result;
				schema = parseResult.result;
			} catch (error) {
				if (isHandledError(error)) $refAdded.value = error;
				throw error;
			}
		}
		if (schema === null || typeof schema !== "object" || Buffer.isBuffer(schema)) throw ono.syntax(`"${this.$refs._root$Ref.path || schema}" is not a valid JSON Schema`);
		this.schema = schema;
		return { schema };
	}
	async parseMany({ arrayBuffer, fetch, pathOrUrlOrSchemas, resolvedInputs: _resolvedInputs }) {
		const resolvedInputs = [..._resolvedInputs || []];
		resolvedInputs.push(...pathOrUrlOrSchemas.map((schema) => getResolvedInput({ pathOrUrlOrSchema: schema })) || []);
		this.schemaMany = [];
		this.schemaManySources = [];
		this.sourcePathToPrefix = /* @__PURE__ */ new Map();
		for (let i = 0; i < resolvedInputs.length; i++) {
			const resolvedInput = resolvedInputs[i];
			const { path, type } = resolvedInput;
			let { schema } = resolvedInput;
			if (schema) {} else if (type !== "json") {
				const file = newFile(path);
				const $refAdded = this.$refs._add(file.url);
				$refAdded.pathType = type;
				try {
					await (type === "file" ? fileResolver : urlResolver).handler({
						arrayBuffer: arrayBuffer?.[i],
						fetch,
						file
					});
					const parseResult = await parseFile(file, this.options.parse);
					$refAdded.value = parseResult.result;
					schema = parseResult.result;
				} catch (error) {
					if (isHandledError(error)) $refAdded.value = error;
					throw error;
				}
			}
			if (schema === null || typeof schema !== "object" || Buffer.isBuffer(schema)) throw ono.syntax(`"${this.$refs._root$Ref.path || schema}" is not a valid JSON Schema`);
			this.schemaMany.push(schema);
			this.schemaManySources.push(path && path.length ? path : cwd());
		}
		return { schemaMany: this.schemaMany };
	}
	mergeMany() {
		const schemas = this.schemaMany || [];
		if (!schemas.length) throw ono("mergeMany called with no schemas. Did you run parseMany?");
		const merged = {};
		let chosenOpenapi;
		let chosenSwagger;
		for (const s of schemas) {
			if (!chosenOpenapi && s && typeof s.openapi === "string") chosenOpenapi = s.openapi;
			if (!chosenSwagger && s && typeof s.swagger === "string") chosenSwagger = s.swagger;
			if (chosenOpenapi && chosenSwagger) break;
		}
		if (typeof chosenOpenapi === "string") merged.openapi = chosenOpenapi;
		else if (typeof chosenSwagger === "string") merged.swagger = chosenSwagger;
		const infoAccumulator = {};
		for (const s of schemas) {
			const info = s?.info;
			if (info && typeof info === "object") {
				for (const [k, v] of Object.entries(info)) if (infoAccumulator[k] === void 0 && v !== void 0) infoAccumulator[k] = JSON.parse(JSON.stringify(v));
			}
		}
		if (Object.keys(infoAccumulator).length) merged.info = infoAccumulator;
		const servers = [];
		const seenServers = /* @__PURE__ */ new Set();
		for (const s of schemas) {
			const arr = s?.servers;
			if (Array.isArray(arr)) {
				for (const srv of arr) if (srv && typeof srv === "object") {
					const key = `${srv.url || ""}|${srv.description || ""}`;
					if (!seenServers.has(key)) {
						seenServers.add(key);
						servers.push(JSON.parse(JSON.stringify(srv)));
					}
				}
			}
		}
		if (servers.length) merged.servers = servers;
		merged.paths = {};
		merged.components = {};
		const componentSections = [
			"schemas",
			"parameters",
			"requestBodies",
			"responses",
			"headers",
			"securitySchemes",
			"examples",
			"links",
			"callbacks"
		];
		for (const sec of componentSections) merged.components[sec] = {};
		const tagNameSet = /* @__PURE__ */ new Set();
		const tags = [];
		const usedOpIds = /* @__PURE__ */ new Set();
		const baseName = (p) => {
			try {
				const parts = p.split("#")[0].split("/");
				const filename = parts[parts.length - 1] || "schema";
				const dot = filename.lastIndexOf(".");
				return (dot > 0 ? filename.substring(0, dot) : filename).replace(/[^A-Za-z0-9_-]/g, "_");
			} catch {
				return "schema";
			}
		};
		const unique = (set, proposed) => {
			let name = proposed;
			let i = 2;
			while (set.has(name)) name = `${proposed}_${i++}`;
			set.add(name);
			return name;
		};
		const rewriteRef = (ref, refMap) => {
			let m = ref.match(/^#\/components\/([^/]+)\/([^/]+)(.*)$/);
			if (m) {
				const base = `#/components/${m[1]}/${m[2]}`;
				const mapped = refMap.get(base);
				if (mapped) return mapped + (m[3] || "");
			}
			m = ref.match(/^#\/definitions\/([^/]+)(.*)$/);
			if (m) {
				const base = `#/components/schemas/${m[1]}`;
				const mapped = refMap.get(base);
				if (mapped) return mapped + (m[2] || "");
			}
			return ref;
		};
		const cloneAndRewrite = (obj, refMap, tagMap, opIdPrefix, basePath) => {
			if (obj === null || obj === void 0) return obj;
			if (Array.isArray(obj)) return obj.map((v) => cloneAndRewrite(v, refMap, tagMap, opIdPrefix, basePath));
			if (typeof obj !== "object") return obj;
			const out = {};
			for (const [k, v] of Object.entries(obj)) if (k === "$ref" && typeof v === "string") {
				const s = v;
				if (s.startsWith("#")) out[k] = rewriteRef(s, refMap);
				else if (getProtocol(s) === void 0) out[k] = resolve(basePath + "#", s);
				else out[k] = s;
			} else if (k === "tags" && Array.isArray(v) && v.every((x) => typeof x === "string")) out[k] = v.map((t) => tagMap.get(t) || t);
			else if (k === "operationId" && typeof v === "string") out[k] = unique(usedOpIds, `${opIdPrefix}_${v}`);
			else out[k] = cloneAndRewrite(v, refMap, tagMap, opIdPrefix, basePath);
			return out;
		};
		for (let i = 0; i < schemas.length; i++) {
			const schema = schemas[i] || {};
			const sourcePath = this.schemaManySources[i] || `multi://input/${i + 1}`;
			const prefix = baseName(sourcePath);
			const withoutHash = stripHash(sourcePath);
			const protocol = getProtocol(withoutHash);
			if (protocol === void 0 || protocol === "file" || protocol === "http" || protocol === "https") this.sourcePathToPrefix.set(withoutHash, prefix);
			const refMap = /* @__PURE__ */ new Map();
			const tagMap = /* @__PURE__ */ new Map();
			const srcComponents = schema.components || {};
			for (const sec of componentSections) {
				const group = srcComponents[sec] || {};
				for (const [name] of Object.entries(group)) {
					const newName = `${prefix}_${name}`;
					refMap.set(`#/components/${sec}/${name}`, `#/components/${sec}/${newName}`);
				}
			}
			const srcTags = Array.isArray(schema.tags) ? schema.tags : [];
			for (const t of srcTags) {
				if (!t || typeof t !== "object" || typeof t.name !== "string") continue;
				const desired = t.name;
				const finalName = tagNameSet.has(desired) ? `${prefix}_${desired}` : desired;
				tagNameSet.add(finalName);
				tagMap.set(desired, finalName);
				if (!tags.find((x) => x && x.name === finalName)) tags.push({
					...t,
					name: finalName
				});
			}
			for (const sec of componentSections) {
				const group = schema.components && schema.components[sec] || {};
				for (const [name, val] of Object.entries(group)) {
					const newName = `${prefix}_${name}`;
					merged.components[sec][newName] = cloneAndRewrite(val, refMap, tagMap, prefix, stripHash(sourcePath));
				}
			}
			const HTTP_METHODS = new Set([
				"delete",
				"get",
				"head",
				"options",
				"patch",
				"post",
				"put",
				"trace"
			]);
			const srcPaths = schema.paths || {};
			for (const [p, item] of Object.entries(srcPaths)) if (merged.paths[p]) {
				const hasMethodConflict = Object.keys(item).filter((k) => HTTP_METHODS.has(k)).some((m) => merged.paths[p][m] !== void 0);
				const rewritten = cloneAndRewrite(item, refMap, tagMap, prefix, stripHash(sourcePath));
				if (hasMethodConflict) {
					const trimmed = p.startsWith("/") ? p.substring(1) : p;
					merged.paths[`/${prefix}/${trimmed}`] = rewritten;
				} else Object.assign(merged.paths[p], rewritten);
			} else merged.paths[p] = cloneAndRewrite(item, refMap, tagMap, prefix, stripHash(sourcePath));
		}
		if (tags.length) merged.tags = tags;
		const rootPath = this.schemaManySources[0] || cwd();
		this.$refs = new $Refs();
		const rootRef = this.$refs._add(rootPath);
		rootRef.pathType = isFileSystemPath(rootPath) ? "file" : "http";
		rootRef.value = merged;
		this.schema = merged;
		return merged;
	}
};
//#endregion
export { $RefParser, InvalidPointerError, JSONParserError, JSONParserErrorGroup, MissingPointerError, ParserError, ResolverError, TimeoutError, UnmatchedParserError, UnmatchedResolverError, getResolvedInput, isHandledError, normalizeError, sendRequest };

//# sourceMappingURL=index.mjs.map