
      import { Temporal } from "@js-temporal/polyfill";
      import { URLPattern } from "urlpattern-polyfill";
      globalThis.addEventListener = () => {};
    
import { Object as Object$1, deno_default, getDocumentLoader, getTypeId, lookupWebFinger } from "./type-BSR-vRXo.js";
import { cloneDeep, delay } from "es-toolkit";
import { getLogger } from "@logtape/logtape";
import { SpanStatusCode, trace } from "@opentelemetry/api";
import { Router } from "uri-template-router";
import { parseTemplate } from "url-template";

//#region src/federation/router.ts
function cloneInnerRouter(router) {
	const clone = new Router();
	clone.nid = router.nid;
	clone.fsm = cloneDeep(router.fsm);
	clone.routeSet = new Set(router.routeSet);
	clone.templateRouteMap = new Map(router.templateRouteMap);
	clone.valueRouteMap = new Map(router.valueRouteMap);
	clone.hierarchy = cloneDeep(router.hierarchy);
	return clone;
}
/**
* URL router and constructor based on URI Template
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
*/
var Router$1 = class Router$1 {
	#router;
	#templates;
	#templateStrings;
	/**
	* Whether to ignore trailing slashes when matching paths.
	* @since 1.6.0
	*/
	trailingSlashInsensitive;
	/**
	* Create a new {@link Router}.
	* @param options Options for the router.
	*/
	constructor(options = {}) {
		this.#router = new Router();
		this.#templates = {};
		this.#templateStrings = {};
		this.trailingSlashInsensitive = options.trailingSlashInsensitive ?? false;
	}
	clone() {
		const clone = new Router$1({ trailingSlashInsensitive: this.trailingSlashInsensitive });
		clone.#router = cloneInnerRouter(this.#router);
		clone.#templates = { ...this.#templates };
		clone.#templateStrings = { ...this.#templateStrings };
		return clone;
	}
	/**
	* Checks if a path name exists in the router.
	* @param name The name of the path.
	* @returns `true` if the path name exists, otherwise `false`.
	*/
	has(name) {
		return name in this.#templates;
	}
	/**
	* Adds a new path rule to the router.
	* @param template The path pattern.
	* @param name The name of the path.
	* @returns The names of the variables in the path pattern.
	*/
	add(template, name) {
		if (!template.startsWith("/")) throw new RouterError("Path must start with a slash.");
		const rule = this.#router.addTemplate(template, {}, name);
		this.#templates[name] = parseTemplate(template);
		this.#templateStrings[name] = template;
		return new Set(rule.variables.map((v) => v.varname));
	}
	/**
	* Resolves a path name and values from a URL, if any match.
	* @param url The URL to resolve.
	* @returns The name of the path and its values, if any match.  Otherwise,
	*          `null`.
	*/
	route(url) {
		let match = this.#router.resolveURI(url);
		if (match == null) {
			if (!this.trailingSlashInsensitive) return null;
			url = url.endsWith("/") ? url.replace(/\/+$/, "") : `${url}/`;
			match = this.#router.resolveURI(url);
			if (match == null) return null;
		}
		return {
			name: match.matchValue,
			template: this.#templateStrings[match.matchValue],
			values: match.params
		};
	}
	/**
	* Constructs a URL/path from a path name and values.
	* @param name The name of the path.
	* @param values The values to expand the path with.
	* @returns The URL/path, if the name exists.  Otherwise, `null`.
	*/
	build(name, values) {
		if (name in this.#templates) return this.#templates[name].expand(values);
		return null;
	}
};
/**
* An error thrown by the {@link Router}.
*/
var RouterError = class extends Error {
	/**
	* Create a new {@link RouterError}.
	* @param message The error message.
	*/
	constructor(message) {
		super(message);
		this.name = "RouterError";
	}
};

//#endregion
//#region src/vocab/handle.ts
/**
* Regular expression to match a fediverse handle in the format `@user@server`
* or `user@server`.  The `user` part can contain alphanumeric characters and
* some special characters except `@`.  The `server` part is all characters
* after the `@` symbol in the middle.
*/
const handleRegexp = /^@?((?:[-A-Za-z0-9._~!$&'()*+,;=]|%[A-Fa-f0-9]{2})+)@([^@]+)$/;
/**
* Parses a fediverse handle in the format `@user@server` or `user@server`.
* The `user` part can contain alphanumeric characters and some special
* characters except `@`.  The `server` part is all characters after the `@`
* symbol in the middle.
*
* @example
* ```typescript
* const handle = parseFediverseHandle("@username@example.com");
* console.log(handle?.username); // "username"
* console.log(handle?.host);     // "example.com"
* ```
*
* @param handle - The fediverse handle string to parse.
* @returns A {@link FediverseHandle} object with `username` and `host`
*          if the input is valid; otherwise `null`.
* @since 1.8.0
*/
function parseFediverseHandle(handle) {
	const match = handleRegexp.exec(handle);
	if (match) return {
		username: match[1],
		host: match[2]
	};
	return null;
}
/**
* Converts a fediverse handle in the format `@user@server` or `user@server`
* to an `acct:` URI, which is a URL-like identifier for ActivityPub actors.
*
* @example
* ```typescript
* const identifier = toAcctUrl("@username@example.com");
* console.log(identifier?.href); // "acct:username@example.com"
* ```
*
* @param handle - The fediverse handle string to convert.
* @returns A `URL` object representing the `acct:` URI if conversion succeeds;
*          otherwise `null`.
* @since 1.8.0
*/
function toAcctUrl(handle) {
	const parsed = parseFediverseHandle(handle);
	if (!parsed) return null;
	const identifier = new URL(`acct:${parsed.username}@${parsed.host}`);
	return identifier;
}

//#endregion
//#region src/vocab/lookup.ts
const logger = getLogger([
	"fedify",
	"vocab",
	"lookup"
]);
/**
* Looks up an ActivityStreams object by its URI (including `acct:` URIs)
* or a fediverse handle (e.g., `@user@server` or `user@server`).
*
* @example
* ``` typescript
* // Look up an actor by its fediverse handle:
* await lookupObject("@hongminhee@fosstodon.org");
* // returning a `Person` object.
*
* // A fediverse handle can omit the leading '@':
* await lookupObject("hongminhee@fosstodon.org");
* // returning a `Person` object.
*
* // A `acct:` URI can be used as well:
* await lookupObject("acct:hongminhee@fosstodon.org");
* // returning a `Person` object.
*
* // Look up an object by its URI:
* await lookupObject("https://todon.eu/@hongminhee/112060633798771581");
* // returning a `Note` object.
*
* // It can be a `URL` object as well:
* await lookupObject(new URL("https://todon.eu/@hongminhee/112060633798771581"));
* // returning a `Note` object.
* ```
*
* @param identifier The URI or fediverse handle to look up.
* @param options Lookup options.
* @returns The object, or `null` if not found.
* @since 0.2.0
*/
async function lookupObject(identifier, options = {}) {
	const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
	const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version);
	return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
		try {
			const result = await lookupObjectInternal(identifier, options);
			if (result == null) span.setStatus({ code: SpanStatusCode.ERROR });
			else {
				if (result.id != null) span.setAttribute("activitypub.object.id", result.id.href);
				span.setAttribute("activitypub.object.type", getTypeId(result).href);
				if (result.replyTargetIds.length > 0) span.setAttribute("activitypub.object.in_reply_to", result.replyTargetIds.map((id) => id.href));
			}
			return result;
		} catch (error) {
			span.setStatus({
				code: SpanStatusCode.ERROR,
				message: String(error)
			});
			throw error;
		} finally {
			span.end();
		}
	});
}
async function lookupObjectInternal(identifier, options = {}) {
	const documentLoader = options.documentLoader ?? getDocumentLoader({ userAgent: options.userAgent });
	if (typeof identifier === "string") identifier = toAcctUrl(identifier) ?? new URL(identifier);
	let document = null;
	if (identifier.protocol === "http:" || identifier.protocol === "https:") try {
		const remoteDoc = await documentLoader(identifier.href, { signal: options.signal });
		document = remoteDoc.document;
	} catch (error) {
		logger.debug("Failed to fetch remote document:\n{error}", { error });
	}
	if (document == null) {
		const jrd = await lookupWebFinger(identifier, {
			userAgent: options.userAgent,
			tracerProvider: options.tracerProvider,
			allowPrivateAddress: "allowPrivateAddress" in options && options.allowPrivateAddress === true,
			signal: options.signal
		});
		if (jrd?.links == null) return null;
		for (const l of jrd.links) {
			if (l.type !== "application/activity+json" && !l.type?.match(/application\/ld\+json;\s*profile="https:\/\/www.w3.org\/ns\/activitystreams"/) || l.rel !== "self" || l.href == null) continue;
			try {
				const remoteDoc = await documentLoader(l.href, { signal: options.signal });
				document = remoteDoc.document;
				break;
			} catch (error) {
				logger.debug("Failed to fetch remote document:\n{error}", { error });
				continue;
			}
		}
	}
	if (document == null) return null;
	try {
		return await Object$1.fromJsonLd(document, {
			documentLoader,
			contextLoader: options.contextLoader,
			tracerProvider: options.tracerProvider
		});
	} catch (error) {
		if (error instanceof TypeError) {
			logger.debug("Failed to parse JSON-LD document: {error}\n{document}", {
				error,
				document
			});
			return null;
		}
		throw error;
	}
}
/**
* Traverses a collection, yielding each item in the collection.
* If the collection is paginated, it will fetch the next page
* automatically.
*
* @example
* ``` typescript
* const collection = await lookupObject(collectionUrl);
* if (collection instanceof Collection) {
*   for await (const item of traverseCollection(collection)) {
*     console.log(item.id?.href);
*   }
* }
* ```
*
* @param collection The collection to traverse.
* @param options Options for traversing the collection.
* @returns An async iterable of each item in the collection.
* @since 1.1.0
*/
async function* traverseCollection(collection, options = {}) {
	if (collection.firstId == null) for await (const item of collection.getItems(options)) yield item;
	else {
		const interval = Temporal.Duration.from(options.interval ?? { seconds: 0 }).total("millisecond");
		let page = await collection.getFirst(options);
		while (page != null) {
			for await (const item of page.getItems(options)) yield item;
			if (interval > 0) await delay(interval);
			page = await page.getNext(options);
		}
	}
}

//#endregion
export { Router$1 as Router, RouterError, lookupObject, traverseCollection };