import { isEqual } from 'lodash';
import { UtilsV1 as UtilsV1Pb } from '@superblocksteam/types';
import unreachable from '../utils/unreachable.js';
import { sha256Base64 } from './hashing.js';
import { stableJSONStringify } from './JSON/index.js';
import { getEnumProtoName } from './proto.js';
export * from './constants.js';
/**
 * Returns the SHA-256 hash of the given value, encoded as a base64 string. The hash is stable, meaning that the same
 * object will always produce the same hash.
 * @param value The value to hash. Must be JSON-serializable.
 * @returns The SHA-256 hash of the given value, encoded as a base64 string.
 */
export async function sha256Object(value) {
    return await sha256Base64(stableJSONStringify(value) ?? '');
}
function splitApplicationSettings(settings) {
    const { registeredComponents, sourceFiles, bundledFiles, cliVersion, componentsLastUploaded, profiles, ...rest } = settings;
    return {
        components: {
            registeredComponents,
            sourceFiles,
            bundledFiles,
            cliVersion,
            componentsLastUploaded
        },
        rest,
        // We should not include the profiles in the hash
        profiles
    };
}
export async function hashApplicationSettings(settings) {
    const { components, rest } = splitApplicationSettings(settings);
    const [componentsHash, restHash] = await Promise.all([sha256Object(components), sha256Object(rest)]);
    return {
        components: componentsHash,
        rest: restHash
    };
}
async function createApplicationSignatureTreeV1({ pageDsl, appSettings }) {
    const hashedSettings = await hashApplicationSettings(appSettings);
    const hashedPage = await sha256Object(pageDsl);
    return {
        v: 1,
        settings: hashedSettings,
        page: {
            version: pageDsl.version,
            hash: hashedPage
        }
    };
}
async function createApplicationSignatureTreeV2({ pages, configuration, appSettings }) {
    const pagesHashes = {};
    for (const [pageId, pageDsl] of Object.entries(pages)) {
        pagesHashes[pageId] = {
            version: pageDsl.version,
            hash: await sha256Object(pageDsl)
        };
    }
    const configurationHash = await sha256Object(configuration);
    const settingsHash = await hashApplicationSettings(appSettings);
    return {
        v: 2,
        pages: pagesHashes,
        configuration: {
            // if configuration.version is not defined, we assume it's 1
            version: configuration?.version ?? 1,
            hash: configurationHash
        },
        settings: settingsHash
    };
}
// TODO: remove this
const produceV1Signatures = false;
export async function createApplicationSignatureTree({ pages, configuration, appSettings }) {
    if (produceV1Signatures) {
        const pagesList = Object.values(pages);
        if (pagesList.length !== 1) {
            throw new Error('Only single page applications are supported in v1 signature tree');
        }
        const pageDsl = pagesList[0];
        return createApplicationSignatureTreeV1({ pageDsl, appSettings });
    }
    else {
        return createApplicationSignatureTreeV2({ pages, configuration, appSettings });
    }
}
/**
 * Upgrades an application signature tree from v1 to v2. The application must have a single page.
 */
export async function upgradeApplicationSignatureTree({ currentHashTree, pageIds, configuration }) {
    switch (currentHashTree.v) {
        case 2:
            return currentHashTree;
        case 1: {
            if (pageIds.length !== 1) {
                throw new Error('Only single page applications are supported in v1 signature tree');
            }
            const configurationHash = await sha256Object(configuration);
            return {
                v: 2,
                pages: { [pageIds[0]]: currentHashTree.page },
                configuration: {
                    // if configuration.version is not defined, we assume it's 1
                    version: configuration.version ?? 1,
                    hash: configurationHash
                },
                settings: currentHashTree.settings
            };
        }
        default:
            unreachable(currentHashTree);
    }
}
async function verifyApplicationHashTreeV1({ hashTree, pages, appSettings }) {
    if (pages) {
        const pagesList = Object.values(pages);
        if (pagesList.length !== 1) {
            throw new Error('Only single page applications are supported in v1 signature tree');
        }
        const pageDsl = pagesList[0];
        const pageHash = await sha256Object(pageDsl);
        if (hashTree.page.hash !== pageHash) {
            return false;
        }
    }
    if (appSettings) {
        const settingsHash = await hashApplicationSettings(appSettings);
        if (!isEqual(hashTree.settings, settingsHash)) {
            return false;
        }
    }
    return true;
}
async function verifyApplicationHashTreeV2({ hashTree, pages, configuration, appSettings }) {
    if (pages) {
        for (const [pageId, pageDsl] of Object.entries(pages)) {
            const pageHash = await sha256Object(pageDsl);
            if (hashTree.pages[pageId]?.hash !== pageHash) {
                return false;
            }
        }
    }
    if (configuration) {
        const configurationHash = await sha256Object(configuration);
        if (hashTree.configuration.hash !== configurationHash) {
            return false;
        }
    }
    if (appSettings) {
        const settingsHash = await hashApplicationSettings(appSettings);
        if (!isEqual(hashTree.settings, settingsHash)) {
            return false;
        }
    }
    return true;
}
/**
 * Verifies the hashes of the different parts of an application against the given hash tree. If a subset of the parts is
 * provided, only those parts will be verified.
 */
export async function verifyApplicationHashTree({ hashTree, pages, configuration, appSettings }) {
    switch (hashTree.v) {
        case 1:
            return verifyApplicationHashTreeV1({ hashTree, pages, appSettings });
        case 2:
            return verifyApplicationHashTreeV2({ hashTree, pages, configuration, appSettings });
        default:
            unreachable(hashTree);
    }
}
export async function updateApplicationHashTree({ currentTree, updates }) {
    if (currentTree.v !== 2) {
        throw new Error(`Unsupported signature tree version: ${currentTree.v}`);
    }
    else {
        let hashTree = currentTree;
        for (const update of updates) {
            switch (update.type) {
                case 'page': {
                    const pageHash = await sha256Object(update.pageDsl);
                    hashTree = {
                        ...hashTree,
                        pages: {
                            ...hashTree.pages,
                            [update.pageId]: {
                                version: update.pageDsl.version,
                                hash: pageHash
                            }
                        }
                    };
                    break;
                }
                case 'delete-page': {
                    if (hashTree.pages[update.pageId]) {
                        hashTree = {
                            ...hashTree,
                            pages: {
                                ...hashTree.pages
                            }
                        };
                        delete hashTree.pages[update.pageId];
                    }
                    break;
                }
                case 'configuration': {
                    const hash = await sha256Object(update.configuration);
                    hashTree = {
                        ...hashTree,
                        configuration: {
                            // if configuration.version is not defined, we assume it's 1
                            version: update.configuration.version ?? 1,
                            hash
                        }
                    };
                    break;
                }
                case 'settings': {
                    const hash = await hashApplicationSettings(update.settings);
                    hashTree = {
                        ...hashTree,
                        settings: hash
                    };
                    break;
                }
                default:
                    unreachable(update);
            }
        }
        return hashTree;
    }
}
export function getAlgorithmName(algorithm) {
    const protoName = getEnumProtoName(UtilsV1Pb.Signature_Algorithm, algorithm);
    if (protoName) {
        return protoName;
    }
    return getEnumProtoName(UtilsV1Pb.Signature_Algorithm, UtilsV1Pb.Signature_Algorithm.UNSPECIFIED) ?? '';
}
//# sourceMappingURL=index.js.map