
          import { Temporal } from "@js-temporal/polyfill";
          import { URLPattern } from "urlpattern-polyfill";
        
import { getUserAgent } from "./docloader-ZGKS1Zy-.js";
import { getLogger } from "@logtape/logtape";

//#region src/nodeinfo/client.ts
const logger = getLogger([
	"fedify",
	"nodeinfo",
	"client"
]);
async function getNodeInfo(url, options = {}) {
	try {
		let nodeInfoUrl = url;
		if (!options.direct) {
			const wellKnownUrl = new URL("/.well-known/nodeinfo", url);
			const wellKnownResponse = await fetch(wellKnownUrl, { headers: {
				Accept: "application/json",
				"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
			} });
			if (!wellKnownResponse.ok) {
				logger.error("Failed to fetch {url}: {status} {statusText}", {
					url: wellKnownUrl.href,
					status: wellKnownResponse.status,
					statusText: wellKnownResponse.statusText
				});
				return void 0;
			}
			const wellKnownRd = await wellKnownResponse.json();
			const link = wellKnownRd?.links?.find((link$1) => link$1 != null && "rel" in link$1 && (link$1.rel === "http://nodeinfo.diaspora.software/ns/schema/2.0" || link$1.rel === "http://nodeinfo.diaspora.software/ns/schema/2.1") && "href" in link$1 && link$1.href != null);
			if (link == null || link.href == null) {
				logger.error("Failed to find a NodeInfo document link from {url}: {resourceDescriptor}", {
					url: wellKnownUrl.href,
					resourceDescriptor: wellKnownRd
				});
				return void 0;
			}
			nodeInfoUrl = link.href;
		}
		const response = await fetch(nodeInfoUrl, { headers: {
			Accept: "application/json",
			"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
		} });
		if (!response.ok) {
			logger.error("Failed to fetch NodeInfo document from {url}: {status} {statusText}", {
				url: nodeInfoUrl.toString(),
				status: response.status,
				statusText: response.statusText
			});
			return void 0;
		}
		const data = await response.json();
		if (options.parse === "none") return data;
		return parseNodeInfo(data, { tryBestEffort: options.parse === "best-effort" }) ?? void 0;
	} catch (error) {
		logger.error("Failed to fetch NodeInfo document from {url}: {error}", {
			url: url.toString(),
			error
		});
		return void 0;
	}
}
/**
* Parses a NodeInfo document.
* @param data A JSON value that complies with the NodeInfo schema.
* @param options Options for parsing the NodeInfo document.
* @returns The parsed NodeInfo document if it is valid.  Otherwise, `null`
*          is returned.
* @since 1.2.0
*/
function parseNodeInfo(data, options = {}) {
	if (typeof data !== "object" || data == null || !("software" in data)) return null;
	const software = parseSoftware(data.software, options);
	if (software == null) return null;
	let protocols = [];
	if ("protocols" in data && Array.isArray(data.protocols)) {
		const ps = data.protocols.map(parseProtocol);
		protocols = ps.filter((p) => p != null);
		if (ps.length != protocols.length && !options.tryBestEffort) return null;
	} else if (!options.tryBestEffort) return null;
	let services;
	if ("services" in data) {
		if (typeof data.services === "object" && data.services != null) {
			const ss = parseServices(data.services, options);
			if (ss == null) {
				if (!options.tryBestEffort) return null;
			} else services = ss;
		} else if (!options.tryBestEffort) return null;
	}
	let openRegistrations;
	if ("openRegistrations" in data) {
		if (typeof data.openRegistrations === "boolean") openRegistrations = data.openRegistrations;
		else if (!options.tryBestEffort) return null;
	}
	let usage = {
		users: {},
		localPosts: 0,
		localComments: 0
	};
	if ("usage" in data) {
		const u = parseUsage(data.usage, options);
		if (u == null) {
			if (!options.tryBestEffort) return null;
		} else usage = u;
	}
	let metadata;
	if ("metadata" in data) {
		if (typeof data.metadata === "object" && data.metadata != null) metadata = Object.fromEntries(Object.entries(data.metadata));
		else if (!options.tryBestEffort) return null;
	}
	const result = {
		software,
		protocols,
		usage
	};
	if (services != null) result.services = services;
	if (openRegistrations != null) result.openRegistrations = openRegistrations;
	if (metadata != null) result.metadata = metadata;
	return result;
}
function parseSoftware(data, options = {}) {
	if (typeof data !== "object" || data == null) {
		if (!options.tryBestEffort) data = {};
		return null;
	}
	let name;
	if ("name" in data && typeof data.name === "string" && data.name.match(/^\s*[A-Za-z0-9-]+\s*$/)) {
		if (!data.name.match(/^[a-z0-9-]+$/) && !options.tryBestEffort) return null;
		name = data.name.trim().toLowerCase();
	} else return null;
	let version;
	if ("version" in data) version = String(data.version);
	else {
		if (!options.tryBestEffort) return null;
		version = "0.0.0";
	}
	let repository;
	if ("repository" in data) {
		if (typeof data.repository === "string") try {
			repository = new URL(data.repository);
		} catch {
			if (!options.tryBestEffort) return null;
		}
		else if (!options.tryBestEffort) return null;
	}
	let homepage;
	if ("homepage" in data) {
		if (typeof data.homepage === "string") try {
			homepage = new URL(data.homepage);
		} catch {
			if (!options.tryBestEffort) return null;
		}
		else if (!options.tryBestEffort) return null;
	}
	const result = {
		name,
		version
	};
	if (repository != null) result.repository = repository;
	if (homepage != null) result.homepage = homepage;
	return result;
}
function parseProtocol(data) {
	if (data === "activitypub" || data === "buddycloud" || data === "dfrn" || data === "diaspora" || data === "libertree" || data === "ostatus" || data === "pumpio" || data === "tent" || data === "xmpp" || data === "zot") return data;
	return null;
}
function parseServices(data, options = {}) {
	if (!(typeof data === "object") || data == null) {
		if (options.tryBestEffort) return {};
		return null;
	}
	let inbound;
	if ("inbound" in data && Array.isArray(data.inbound)) {
		const is = data.inbound.map(parseInboundService);
		inbound = is.filter((i) => i != null);
		if (is.length > inbound.length && !options.tryBestEffort) return null;
	}
	let outbound;
	if ("outbound" in data && Array.isArray(data.outbound)) {
		const os = data.outbound.map(parseOutboundService);
		outbound = os.filter((o) => o != null);
		if (os.length > outbound.length && !options.tryBestEffort) return null;
	}
	const result = {};
	if (inbound != null) result.inbound = inbound;
	if (outbound != null) result.outbound = outbound;
	return result;
}
function parseInboundService(data) {
	if (data === "atom1.0" || data === "gnusocial" || data === "imap" || data === "pnut" || data === "pop3" || data === "pumpio" || data === "rss2.0" || data === "twitter") return data;
	return null;
}
function parseOutboundService(data) {
	if (data === "atom1.0" || data === "blogger" || data === "buddycloud" || data === "diaspora" || data === "dreamwidth" || data === "drupal" || data === "facebook" || data === "friendica" || data === "gnusocial" || data === "google" || data === "insanejournal" || data === "libertree" || data === "linkedin" || data === "livejournal" || data === "mediagoblin" || data === "myspace" || data === "pinterest" || data === "pnut" || data === "posterous" || data === "pumpio" || data === "redmatrix" || data === "rss2.0" || data === "smtp" || data === "tent" || data === "tumblr" || data === "twitter" || data === "wordpress" || data === "xmpp") return data;
	return null;
}
function parseUsage(data, options = {}) {
	if (typeof data !== "object" || data == null) return null;
	const users = {};
	if ("users" in data && typeof data.users === "object" && data.users != null) {
		if ("total" in data.users) if (typeof data.users.total === "number") users.total = data.users.total;
		else {
			if (!options.tryBestEffort) return null;
			if (typeof data.users.total === "string") {
				const n = parseInt(data.users.total);
				if (!isNaN(n)) users.total = n;
			}
		}
		if ("activeHalfyear" in data.users) if (typeof data.users.activeHalfyear === "number") users.activeHalfyear = data.users.activeHalfyear;
		else {
			if (!options.tryBestEffort) return null;
			if (typeof data.users.activeHalfyear === "string") {
				const n = parseInt(data.users.activeHalfyear);
				if (!isNaN(n)) users.activeHalfyear = n;
			}
		}
		if ("activeMonth" in data.users) if (typeof data.users.activeMonth === "number") users.activeMonth = data.users.activeMonth;
		else {
			if (!options.tryBestEffort) return null;
			if (typeof data.users.activeMonth === "string") {
				const n = parseInt(data.users.activeMonth);
				if (!isNaN(n)) users.activeMonth = n;
			}
		}
	} else if (!options.tryBestEffort) return null;
	let localPosts = 0;
	if ("localPosts" in data) if (typeof data.localPosts === "number") localPosts = data.localPosts;
	else {
		if (!options.tryBestEffort) return null;
		if (typeof data.localPosts === "string") {
			const n = parseInt(data.localPosts);
			if (!isNaN(n)) localPosts = n;
		}
	}
	let localComments = 0;
	if ("localComments" in data) if (typeof data.localComments === "number") localComments = data.localComments;
	else {
		if (!options.tryBestEffort) return null;
		if (typeof data.localComments === "string") {
			const n = parseInt(data.localComments);
			if (!isNaN(n)) localComments = n;
		}
	}
	return {
		users,
		localPosts,
		localComments
	};
}

//#endregion
//#region src/nodeinfo/types.ts
/**
* Converts a {@link NodeInfo} object to a JSON value.
* @param nodeInfo The {@link NodeInfo} object to convert.
* @returns The JSON value that complies with the NodeInfo schema.
* @throws {TypeError} If the {@link NodeInfo} object is invalid.
*/
function nodeInfoToJson(nodeInfo) {
	if (!nodeInfo.software.name.match(/^[a-z0-9-]+$/)) throw new TypeError("Invalid software name.");
	if (nodeInfo.protocols.length < 1) throw new TypeError("At least one protocol must be supported.");
	if (nodeInfo.usage.users.total != null && (nodeInfo.usage.users.total < 0 || !Number.isInteger(nodeInfo.usage.users.total))) throw new TypeError("Invalid total users.");
	if (nodeInfo.usage.users.activeHalfyear != null && (nodeInfo.usage.users.activeHalfyear < 0 || !Number.isInteger(nodeInfo.usage.users.activeHalfyear))) throw new TypeError("Invalid active halfyear users.");
	if (nodeInfo.usage.users.activeMonth != null && (nodeInfo.usage.users.activeMonth < 0 || !Number.isInteger(nodeInfo.usage.users.activeMonth))) throw new TypeError("Invalid active month users.");
	if (nodeInfo.usage.localPosts < 0 || !Number.isInteger(nodeInfo.usage.localPosts)) throw new TypeError("Invalid local posts.");
	if (nodeInfo.usage.localComments < 0 || !Number.isInteger(nodeInfo.usage.localComments)) throw new TypeError("Invalid local comments.");
	return {
		"$schema": "http://nodeinfo.diaspora.software/ns/schema/2.1#",
		version: "2.1",
		software: {
			name: nodeInfo.software.name,
			version: nodeInfo.software.version,
			repository: nodeInfo.software.repository?.href,
			homepage: nodeInfo.software.homepage?.href
		},
		protocols: nodeInfo.protocols,
		services: nodeInfo.services == null ? {
			inbound: [],
			outbound: []
		} : {
			inbound: nodeInfo.services.inbound ?? [],
			outbound: nodeInfo.services.outbound ?? []
		},
		openRegistrations: nodeInfo.openRegistrations ?? false,
		usage: {
			users: nodeInfo.usage.users,
			localPosts: nodeInfo.usage.localPosts,
			localComments: nodeInfo.usage.localComments
		},
		metadata: nodeInfo.metadata ?? {}
	};
}

//#endregion
export { getNodeInfo, nodeInfoToJson, parseNodeInfo };