var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { utils as didUtils } from '@enbox/dids';
import { DateSort, DwnInterfaceName, DwnMethodName, Jws, Message } from '@enbox/dwn-sdk-js';
import { normalizeBaseUrl } from './local-dwn.js';
export function getDwnServiceEndpointUrls(didUri, dereferencer) {
    return __awaiter(this, void 0, void 0, function* () {
        // Attempt to dereference the DID service with ID fragment #dwn.
        const dereferencingResult = yield dereferencer.dereference(`${didUri}#dwn`);
        if (dereferencingResult.dereferencingMetadata.error) {
            throw new Error(`Failed to dereference '${didUri}#dwn': ${dereferencingResult.dereferencingMetadata.error}`);
        }
        if (didUtils.isDwnDidService(dereferencingResult.contentStream)) {
            const { serviceEndpoint } = dereferencingResult.contentStream;
            const serviceEndpointUrls = typeof serviceEndpoint === 'string'
                // If the service endpoint is a string, format it as a single-element array.
                ? [serviceEndpoint]
                : Array.isArray(serviceEndpoint) && serviceEndpoint.every(endpoint => typeof endpoint === 'string')
                    // If the service endpoint is an array of strings, use it as is.
                    ? serviceEndpoint
                    // If the service endpoint is neither a string nor an array of strings, return an empty array.
                    : [];
            if (serviceEndpointUrls.length > 0) {
                return [...new Set(serviceEndpointUrls.map(normalizeDwnServiceEndpointUrl))];
            }
        }
        // If the DID service with ID fragment #dwn was not found or is not valid, return an empty array.
        return [];
    });
}
function normalizeDwnServiceEndpointUrl(endpoint) {
    try {
        return normalizeBaseUrl(new URL(endpoint).toString());
    }
    catch (_a) {
        return normalizeBaseUrl(endpoint);
    }
}
export function getRecordAuthor(record) {
    return Message.getAuthor(record);
}
/**
 * Get the `protocolRole` string from the signature payload of the given RecordsWriteMessage or RecordsDeleteMessage.
 */
export function getRecordProtocolRole(message) {
    const signaturePayload = Jws.decodePlainObjectPayload(message.authorization.signature);
    return signaturePayload === null || signaturePayload === void 0 ? void 0 : signaturePayload.protocolRole;
}
export function isRecordsWrite(obj) {
    // Validate that the given value is an object.
    if (!obj || typeof obj !== 'object' || obj === null) {
        return false;
    }
    // Validate that the object has the necessary properties of RecordsWrite.
    return ('message' in obj && typeof obj.message === 'object' && obj.message !== null &&
        'descriptor' in obj.message && typeof obj.message.descriptor === 'object' && obj.message.descriptor !== null &&
        'interface' in obj.message.descriptor && obj.message.descriptor.interface === DwnInterfaceName.Records &&
        'method' in obj.message.descriptor && obj.message.descriptor.method === DwnMethodName.Write);
}
/**
 * Get the CID of the given RecordsWriteMessage.
 */
export function getRecordMessageCid(message) {
    return Message.getCid(message);
}
/**
 *  Get the pagination cursor for the given RecordsWriteMessage and DateSort.
 *
 * @param message The RecordsWriteMessage for which to get the pagination cursor.
 * @param dateSort The date sort that will be used in the query or subscription to which the cursor will be applied.
 */
export function getPaginationCursor(message, dateSort) {
    return __awaiter(this, void 0, void 0, function* () {
        const value = dateSort === DateSort.CreatedAscending || dateSort === DateSort.CreatedDescending ?
            message.descriptor.dateCreated : message.descriptor.datePublished;
        if (value === undefined) {
            throw new Error('The dateCreated or datePublished property is missing from the record descriptor.');
        }
        return {
            messageCid: yield getRecordMessageCid(message),
            value
        };
    });
}
/**
 * Polling function with interval, TTL accepting a custom fetch function
 * @template T - the return you expect from the fetcher
 * @param fetchFunction an http fetch function
 * @param [interval=3000] how frequently to poll
 * @param [ttl=300_000] how long until polling stops
 * @returns T - the result of fetch
 */
export function pollWithTtl(fetchFunction, interval = 3000, ttl = 300000, abortSignal) {
    const endTime = Date.now() + ttl;
    let timeoutId = null;
    let isPolling = true;
    return new Promise((resolve, reject) => {
        if (abortSignal) {
            abortSignal.addEventListener('abort', () => {
                isPolling = false;
                if (timeoutId !== null) {
                    clearTimeout(timeoutId);
                }
                console.log('Polling aborted by user');
                resolve(null);
            });
        }
        function poll() {
            return __awaiter(this, void 0, void 0, function* () {
                if (!isPolling) {
                    return;
                }
                const remainingTime = endTime - Date.now();
                if (remainingTime <= 0) {
                    isPolling = false;
                    console.log('Polling stopped: TTL reached');
                    resolve(null);
                    return;
                }
                console.log(`Polling... (Remaining time: ${Math.ceil(remainingTime / 1000)}s)`);
                try {
                    const response = yield fetchFunction();
                    if (response.ok) {
                        isPolling = false;
                        if (timeoutId !== null) {
                            clearTimeout(timeoutId);
                        }
                        console.log('Polling stopped: Success condition met');
                        resolve(response);
                        return;
                    }
                }
                catch (error) {
                    console.error('Error fetching data:', error);
                    reject(error);
                }
                if (isPolling) {
                    timeoutId = setTimeout(poll, interval);
                }
            });
        }
        poll();
    });
}
/**
 * Map over an array with bounded concurrency, preserving input order in the
 * output array. Uses a sliding-window pool of workers so the next item is
 * picked up as soon as any in-flight task settles — strictly better than a
 * fixed chunked-batch pattern (no stalling on the slowest item per batch).
 *
 * Semantics match `Promise.all`: the returned promise rejects on the first
 * task rejection. Use {@link mapConcurrentSettled} when individual failures
 * should not abort the whole batch (e.g. best-effort fan-out).
 *
 * @param items Input items to map over.
 * @param concurrency Maximum number of in-flight tasks. Must be >= 1.
 * @param fn Per-item async function. Receives the item and its index.
 * @returns An array of results in the same order as `items`.
 */
export function mapConcurrent(items, concurrency, fn) {
    return __awaiter(this, void 0, void 0, function* () {
        if (!Number.isInteger(concurrency) || concurrency < 1) {
            throw new Error(`mapConcurrent: concurrency must be a positive integer, got ${concurrency}`);
        }
        if (items.length === 0) {
            return [];
        }
        const results = new Array(items.length);
        let next = 0;
        const worker = () => __awaiter(this, void 0, void 0, function* () {
            while (true) {
                const i = next++;
                if (i >= items.length) {
                    return;
                }
                results[i] = yield fn(items[i], i);
            }
        });
        const workerCount = Math.min(concurrency, items.length);
        const workers = [];
        for (let w = 0; w < workerCount; w++) {
            workers.push(worker());
        }
        yield Promise.all(workers);
        return results;
    });
}
/**
 * Settled variant of {@link mapConcurrent}: never rejects — every task's
 * outcome is captured as a `PromiseSettledResult`. Use this for best-effort
 * fan-outs where partial success is acceptable.
 */
export function mapConcurrentSettled(items, concurrency, fn) {
    return __awaiter(this, void 0, void 0, function* () {
        return mapConcurrent(items, concurrency, (item, index) => __awaiter(this, void 0, void 0, function* () {
            try {
                const value = yield fn(item, index);
                return { status: 'fulfilled', value };
            }
            catch (reason) {
                return { status: 'rejected', reason };
            }
        }));
    });
}
//# sourceMappingURL=utils.js.map