/**
 * Module-level helpers for SyncController.
 *
 * Pure utility functions extracted from SyncController to keep the class
 * file within the 500 LOC ceiling.
 *
 * @module domain/services/controllers/syncHelpers
 */
import SyncError from "../../errors/SyncError.js";
import { signSyncRequest, canonicalizePath } from "../sync/SyncAuthService.js";
// ── Exports ─────────────────────────────────────────────────────────────────
/**
 * Compares two string→string Maps for value equality without allocating.
 *
 * @param a - First map
 * @param b - Second map
 * @returns True if every key in `a` has the same value in `b`
 */
export function mapsEqual(a, b) {
    for (const [k, v] of a) {
        if (b.get(k) !== v) {
            return false;
        }
    }
    return true;
}
/**
 * Normalizes a sync endpoint path to ensure it starts with '/'.
 * Returns '/sync' if no path is provided.
 *
 * @param path - The sync path to normalize
 * @returns Normalized path starting with '/'
 */
export function normalizeSyncPath(path) {
    if (path === undefined || path === null || path === '') {
        return '/sync';
    }
    return path.startsWith('/') ? path : `/${path}`;
}
function hasProcessSyncRequest(value) {
    return 'processSyncRequest' in value && typeof value.processSyncRequest === 'function';
}
function isSyncRequestProcessor(remote) {
    if (typeof remote !== 'object' || remote === null) {
        return false;
    }
    return hasProcessSyncRequest(remote);
}
function isSyncPeer(remote) {
    if (typeof remote !== 'object' || remote === null) {
        return false;
    }
    if (!('sync' in remote)) {
        return false;
    }
    const { sync } = remote;
    if (typeof sync !== 'object' || sync === null) {
        return false;
    }
    return hasProcessSyncRequest(sync);
}
/**
 * Resolves a sync remote into either a direct peer or an HTTP URL target.
 *
 * @param remote - URL string, URL object, or a peer graph instance
 * @param path - Sync endpoint path
 * @param hasPathOverride - Whether `path` was explicitly provided by the caller
 * @returns An object indicating peer type and resolved URL
 */
export function resolveSyncTarget(remote, path, hasPathOverride) {
    if (isSyncRequestProcessor(remote)) {
        return { kind: 'peer', peer: remote };
    }
    if (isSyncPeer(remote)) {
        return { kind: 'peer', peer: remote.sync };
    }
    let targetUrl;
    try {
        targetUrl = new URL(remote);
    }
    catch {
        throw new SyncError('Invalid remote URL', {
            code: 'E_SYNC_REMOTE_URL',
            context: { remote },
        });
    }
    if (!['http:', 'https:'].includes(targetUrl.protocol)) {
        throw new SyncError('Unsupported remote URL protocol', {
            code: 'E_SYNC_REMOTE_URL',
            context: { protocol: targetUrl.protocol },
        });
    }
    const normalizedPath = normalizeSyncPath(path);
    if (!targetUrl.pathname || targetUrl.pathname === '/') {
        targetUrl.pathname = normalizedPath;
    }
    else if (hasPathOverride) {
        targetUrl.pathname = normalizedPath;
    }
    targetUrl.hash = '';
    return { kind: 'url', targetUrl };
}
/**
 * Resolves the effective SyncTrustGate for a sync operation,
 * preferring per-call trust overrides when present.
 *
 * @param host - The SyncHost instance
 * @param defaultGate - The controller's default trust gate
 * @param options - Options object that may contain a `trust` override
 * @returns The resolved trust gate, or null
 */
export function resolveSyncTrustGate(host, defaultGate, options) {
    if (!Object.prototype.hasOwnProperty.call(options, 'trust') ||
        typeof host._createSyncTrustGate !== 'function') {
        return defaultGate;
    }
    return host._createSyncTrustGate(options.trust);
}
/**
 * Builds auth headers for an outgoing sync request if auth is configured.
 *
 * @param params - Auth parameters
 * @returns HTTP headers map (empty if no auth configured)
 */
export async function buildSyncAuthHeaders(params) {
    const { auth, bodyStr, targetUrl, crypto, lamport } = params;
    if (auth === undefined || auth.secret === undefined) {
        return {};
    }
    const bodyBuf = new TextEncoder().encode(bodyStr);
    return await signSyncRequest({
        method: 'POST',
        path: canonicalizePath(targetUrl.pathname + (targetUrl.search || '')),
        contentType: 'application/json',
        body: bodyBuf,
        secret: auth.secret,
        keyId: auth.keyId !== undefined && auth.keyId !== '' ? auth.keyId : 'default',
        lamport,
    }, { crypto });
}
