export const PARHELIA_EDITING_BRIDGE_FAMILY = "parhelia/editing-bridge";
export const PARHELIA_EDITING_BRIDGE_VERSION = {
    major: 1,
    minor: 9,
};
/**
 * Capabilities are an OPEN set of string names. The editor and host each
 * advertise the names they support; the effective session set is the
 * intersection (see `negotiateCapabilities`). Unknown names are ignored by
 * older peers, so adding a capability is always a backwards-compatible (minor)
 * change. `BRIDGE_CAPABILITIES` lists the well-known names for type-safe use.
 */
export const BRIDGE_CAPABILITIES = {
    structure: "structure",
    geometry: "geometry",
    selection: "selection",
    inlineEditing: "inlineEditing",
    richTextCommands: "richTextCommands",
    captureDom: "captureDom",
    refresh: "refresh",
    zoom: "zoom",
    // Host detects component-body drag gestures and forwards them as
    // dragGesture*/dragSnapshot events (pointer-based component drag).
    pointerDrag: "pointerDrag",
    // Host shifts placeholder siblings via transforms to open a gap during a drag
    // (the "make room" animation), driven by the previewGap command.
    dragPreview: "dragPreview",
};
const commandNames = new Set([
    "hello",
    "init",
    "refresh",
    "setSelection",
    "scrollIntoView",
    "scrollBy",
    "queryGeometry",
    "beginInlineEdit",
    "applyFieldPatch",
    "applyRichTextCommand",
    "textCompletion",
    "commitInlineEdit",
    "cancelInlineEdit",
    "setPreviewMode",
    "setEditorMode",
    "setLayoutKind",
    "setChromeVisibility",
    "setZoom",
    "captureDom",
    "previewGap",
    "captureComponentSnapshot",
    "cancelPointerDrag",
]);
const eventNames = new Set([
    "ready",
    "structureUpdated",
    "pageSkeletonUpdated",
    "geometryUpdated",
    "scrollChanged",
    "resizeChanged",
    "interaction",
    "selectionChanged",
    "fieldValueChanged",
    "inlineEditEnded",
    "refreshStarted",
    "refreshCompleted",
    "renderError",
    "domUpdated",
    "captureDomResult",
    "dragGestureStarted",
    "dragGestureMoved",
    "dragGestureEnded",
    "dragSnapshot",
    "componentSnapshotResult",
]);
export function isKnownBridgeCommandName(value) {
    return typeof value === "string" && commandNames.has(value);
}
export function isKnownBridgeEventName(value) {
    return typeof value === "string" && eventNames.has(value);
}
export function createBridgeEnvelope(name, payload, options = {}) {
    const direction = options.direction ??
        (isKnownBridgeCommandName(name) ? "parent-to-host" : "host-to-parent");
    return {
        family: PARHELIA_EDITING_BRIDGE_FAMILY,
        version: PARHELIA_EDITING_BRIDGE_VERSION,
        direction,
        id: options.id ?? createMessageId(),
        name,
        timestamp: options.timestamp ?? Date.now(),
        requestId: options.requestId,
        payload,
    };
}
export function isBridgeEnvelope(value) {
    if (!isRecord(value))
        return false;
    if (value.family !== PARHELIA_EDITING_BRIDGE_FAMILY)
        return false;
    if (!isBridgeVersion(value.version))
        return false;
    if (value.direction !== "parent-to-host" &&
        value.direction !== "host-to-parent") {
        return false;
    }
    if (typeof value.id !== "string" || value.id.length === 0)
        return false;
    if (typeof value.name !== "string" || value.name.length === 0)
        return false;
    if (typeof value.timestamp !== "number" ||
        !Number.isFinite(value.timestamp)) {
        return false;
    }
    if ("requestId" in value &&
        value.requestId !== undefined &&
        typeof value.requestId !== "string") {
        return false;
    }
    return "payload" in value;
}
export function isBridgeVersion(value) {
    return (isRecord(value) &&
        typeof value.major === "number" &&
        Number.isFinite(value.major) &&
        typeof value.minor === "number" &&
        Number.isFinite(value.minor));
}
/** Compatible when the major version matches; minor differences are additive. */
export function isCompatibleBridgeVersion(peerVersion) {
    return (isBridgeVersion(peerVersion) &&
        peerVersion.major === PARHELIA_EDITING_BRIDGE_VERSION.major);
}
/** The effective minor for a session is the lower of the two peers' minors. */
export function negotiateMinor(peerVersion) {
    return Math.min(peerVersion.minor, PARHELIA_EDITING_BRIDGE_VERSION.minor);
}
export function formatBridgeVersion(value) {
    return isBridgeVersion(value) ? `${value.major}.${value.minor}` : "unknown";
}
/** Intersection of two capability sets, preserving the order of the first. */
export function negotiateCapabilities(editorSupported, hostAdvertised) {
    const advertised = new Set(hostAdvertised);
    return editorSupported.filter((capability) => advertised.has(capability));
}
/**
 * Recognition-only guard: a well-formed bridge envelope from the host,
 * regardless of whether the event name is known. RECOGNIZE messages with this;
 * DISPATCH them with `isKnownBridgeEventName` separately, so that unknown (e.g.
 * newer) events surface in diagnostics instead of being silently dropped.
 */
export function isBridgeMessageFromHost(value) {
    return isBridgeEnvelope(value) && value.direction === "host-to-parent";
}
/** Recognition-only guard: a well-formed bridge envelope from the parent. */
export function isBridgeMessageFromParent(value) {
    return isBridgeEnvelope(value) && value.direction === "parent-to-host";
}
export function isBridgeCommandEnvelope(value) {
    return (isBridgeMessageFromParent(value) && isKnownBridgeCommandName(value.name));
}
export function isBridgeEventEnvelope(value) {
    return isBridgeMessageFromHost(value) && isKnownBridgeEventName(value.name);
}
export function normalizeOrigin(value) {
    try {
        return new URL(value).origin;
    }
    catch {
        try {
            return new URL(value, "http://placeholder.local").origin;
        }
        catch {
            return undefined;
        }
    }
}
export function isAllowedOrigin(origin, allowedOrigins) {
    const normalizedOrigin = normalizeOrigin(origin);
    if (!normalizedOrigin)
        return false;
    return allowedOrigins.some((allowedOrigin) => {
        const normalizedAllowed = normalizeOrigin(allowedOrigin);
        return normalizedAllowed === normalizedOrigin;
    });
}
export function assertAllowedOrigin(origin, allowedOrigins) {
    if (!isAllowedOrigin(origin, allowedOrigins)) {
        throw new Error(`Rejected Parhelia bridge message from origin: ${origin}`);
    }
}
function createMessageId() {
    const cryptoObject = globalThis.crypto;
    if (cryptoObject?.randomUUID) {
        return cryptoObject.randomUUID();
    }
    return `bridge-${Date.now().toString(36)}-${Math.random()
        .toString(36)
        .slice(2)}`;
}
function isRecord(value) {
    return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
//# sourceMappingURL=protocol.js.map