import { fromBech32, toHex } from '@cosmjs/encoding';
import { Registry } from '@cosmjs/proto-signing';
import { defaultRegistryTypes } from '@cosmjs/stargate';
import { MsgInstallBundle, MsgInstallBundleResponse, MsgSendChunk, } from '@agoric/cosmic-proto/codegen/agoric/swingset/msgs.js';
export const bundleRegistryTypes = [
    [MsgInstallBundle.typeUrl, MsgInstallBundle],
    [MsgSendChunk.typeUrl, MsgSendChunk],
];
export const makeBundleRegistry = (baseTypes = defaultRegistryTypes) => new Registry([...baseTypes, ...bundleRegistryTypes]);
export const encodeBundle = (bundleJson) => new TextEncoder().encode(bundleJson);
export const defaultSha512 = async (bytes) => {
    const subtle = globalThis.crypto?.subtle;
    if (!subtle) {
        throw new Error('crypto.subtle is required; provide a sha512 function');
    }
    const digest = await subtle.digest('SHA-512', bytes);
    return new Uint8Array(digest);
};
export const getSha512Hex = async (bytes, { sha512 = defaultSha512 } = {}) => {
    const digest = await sha512(bytes);
    return toHex(digest);
};
export const validateBundleJson = (bundleString) => {
    let bundleObject;
    try {
        bundleObject = JSON.parse(bundleString);
    }
    catch (error) {
        throw new Error(`The submitted file is not in the expected format, not parsable as JSON: ${error instanceof Error ? error.message : String(error)}`);
    }
    const { moduleFormat, endoZipBase64, endoZipBase64Sha512 } = bundleObject;
    if (moduleFormat !== 'endoZipBase64') {
        throw new Error(`The submitted file does not have the expected moduleFormat value of endoZipBase64, got: ${moduleFormat}`);
    }
    if (typeof endoZipBase64 !== 'string') {
        throw new Error('The submitted file does not have the expected endoZipBase64 property');
    }
    if (typeof endoZipBase64Sha512 !== 'string') {
        throw new Error('The submitted file does not have the expected endoZipBase64Sha512 property');
    }
    return bundleObject;
};
export const chunkBundle = async (bytes, chunkSizeLimit, { sha512 = defaultSha512 } = {}) => {
    if (!Number.isSafeInteger(chunkSizeLimit) || chunkSizeLimit <= 0) {
        throw new Error(`chunkSizeLimit must be a positive safe integer, got ${chunkSizeLimit}`);
    }
    const bundleSha512Hex = await getSha512Hex(bytes, { sha512 });
    const chunks = [];
    const info = [];
    for (let i = 0; i < bytes.byteLength; i += chunkSizeLimit) {
        const chunk = bytes.subarray(i, Math.min(bytes.byteLength, i + chunkSizeLimit));
        const chunkSha512Hex = await getSha512Hex(chunk, { sha512 });
        chunks.push(chunk);
        info.push({
            sha512: chunkSha512Hex,
            sizeBytes: BigInt(chunk.byteLength),
            state: 0,
        });
    }
    return {
        chunks,
        manifest: {
            sha512: bundleSha512Hex,
            sizeBytes: BigInt(bytes.byteLength),
            chunks: info,
        },
    };
};
export const makeInstallBundleMsg = ({ compressedBundle, uncompressedSize, chunkedArtifact, submitter, }) => ({
    typeUrl: MsgInstallBundle.typeUrl,
    value: {
        uncompressedSize,
        submitter: fromBech32(submitter).data,
        ...(compressedBundle ? { compressedBundle } : {}),
        ...(chunkedArtifact ? { chunkedArtifact } : {}),
    },
});
export const makeSendChunkMsg = ({ chunkedArtifactId, chunkIndex, chunkData, submitter, }) => ({
    typeUrl: MsgSendChunk.typeUrl,
    value: {
        chunkedArtifactId,
        chunkIndex,
        chunkData,
        submitter: fromBech32(submitter).data,
    },
});
export const installBundle = async (params) => {
    const { bundleJson, chunkSizeLimit, submitter, gzip, signAndBroadcast, sha512, makeInstallBundleMsg: makeInstallBundleMsgParam = makeInstallBundleMsg, makeSendChunkMsg: makeSendChunkMsgParam = makeSendChunkMsg, watchBundle, onProgress, } = params;
    const bundleObject = validateBundleJson(bundleJson);
    const { endoZipBase64Sha512 } = bundleObject;
    const uncompressedBundleBytes = encodeBundle(bundleJson);
    const compressedBundleBytes = await gzip(uncompressedBundleBytes);
    const compressedSize = compressedBundleBytes.byteLength;
    const uncompressedSize = uncompressedBundleBytes.byteLength;
    const shouldChunk = compressedBundleBytes.byteLength > chunkSizeLimit;
    onProgress?.({
        type: 'preflight',
        bundleHash: endoZipBase64Sha512,
        uncompressedSize,
        compressedSize,
        chunked: shouldChunk,
        chunkCount: shouldChunk
            ? Math.ceil(compressedBundleBytes.byteLength / chunkSizeLimit)
            : undefined,
    });
    let blockHeight;
    let chunkedArtifactId;
    if (!shouldChunk) {
        const txMsg = makeInstallBundleMsgParam({
            compressedBundle: compressedBundleBytes,
            uncompressedSize: BigInt(uncompressedSize),
            submitter,
        });
        try {
            const txResponse = await signAndBroadcast(txMsg);
            if (!txResponse) {
                throw new Error('no response for bundle');
            }
            blockHeight = txResponse.height;
            onProgress?.({
                type: 'bundle-submitted',
                bundleHash: endoZipBase64Sha512,
                height: blockHeight,
            });
        }
        catch (error) {
            throw new Error(`Transaction failed to submit bundle to chain: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    else {
        const { chunks, manifest } = await chunkBundle(compressedBundleBytes, chunkSizeLimit, { sha512 });
        const txMsg = makeInstallBundleMsgParam({
            uncompressedSize: BigInt(uncompressedSize),
            submitter,
            chunkedArtifact: manifest,
        });
        try {
            const txResponse = await signAndBroadcast(txMsg);
            if (!txResponse) {
                throw new Error(`No transaction response for attempt to submit manifest for bundle ${endoZipBase64Sha512}`);
            }
            blockHeight = txResponse.height;
            const installBundleResponse = txResponse.msgResponses.find(response => response.typeUrl === MsgInstallBundleResponse.typeUrl);
            if (!installBundleResponse) {
                throw new Error(`No install bundle response found in manifest submission transaction response for bundle ${endoZipBase64Sha512}. This is a software defect. Please report.`);
            }
            const { chunkedArtifactId: decodedChunkedArtifactId } = MsgInstallBundleResponse.decode(installBundleResponse.value);
            if (decodedChunkedArtifactId === null ||
                decodedChunkedArtifactId === undefined ||
                decodedChunkedArtifactId === 0n ||
                decodedChunkedArtifactId === 0) {
                throw new Error(`No chunked artifact identifier found in manifest submission transaction response for bundle ${endoZipBase64Sha512}. This is a software defect. Please report.`);
            }
            chunkedArtifactId = BigInt(decodedChunkedArtifactId);
            onProgress?.({
                type: 'manifest-submitted',
                bundleHash: endoZipBase64Sha512,
                height: blockHeight,
                chunkCount: chunks.length,
                chunkedArtifactId,
            });
        }
        catch (error) {
            throw new Error(`Transaction failed to submit bundle manifest to chain for bundle ${endoZipBase64Sha512}: ${error instanceof Error ? error.message : String(error)}`);
        }
        if (chunkedArtifactId === undefined || chunkedArtifactId === 0n) {
            throw new Error(`No chunked artifact identifier found in manifest submission transaction response for bundle ${endoZipBase64Sha512}. This is a software defect. Please report.`);
        }
        for (let i = 0; i < chunks.length; i += 1) {
            const chunk = chunks[i];
            const chunkMsg = makeSendChunkMsgParam({
                chunkedArtifactId,
                chunkIndex: BigInt(i),
                chunkData: chunk,
                submitter,
            });
            try {
                const txResponse = await signAndBroadcast(chunkMsg);
                if (!txResponse) {
                    throw new Error('no transaction response');
                }
                blockHeight = txResponse.height;
                onProgress?.({
                    type: 'chunk-submitted',
                    bundleHash: endoZipBase64Sha512,
                    height: blockHeight,
                    index: i,
                    total: chunks.length,
                });
            }
            catch (error) {
                throw new Error(`Transaction failed to submit bundle chunk ${i} of bundle ${endoZipBase64Sha512} to chain: ${error instanceof Error ? error.message : String(error)}`);
            }
        }
    }
    if (blockHeight === undefined) {
        throw new Error('Bundle submitted but transaction response did not provide a block height. This should not occur. Please report.');
    }
    onProgress?.({
        type: 'watching',
        bundleHash: endoZipBase64Sha512,
        height: blockHeight,
    });
    if (watchBundle) {
        await watchBundle(endoZipBase64Sha512, blockHeight);
    }
    return {
        bundleHash: endoZipBase64Sha512,
        blockHeight,
        chunked: shouldChunk,
        chunkCount: shouldChunk
            ? Math.ceil(compressedBundleBytes.byteLength / chunkSizeLimit)
            : undefined,
        compressedSize,
        uncompressedSize,
        chunkedArtifactId,
    };
};
//# sourceMappingURL=bundle-utils.js.map