"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Root = void 0;
const util_1 = __importDefault(require("util"));
const base_1 = require("./base");
const error_1 = require("./error");
const key_1 = require("./key");
const role_1 = require("./role");
const utils_1 = require("./utils");
/**
 * A container for the signed part of root metadata.
 *
 * The top-level role and metadata file signed by the root keys.
 * This role specifies trusted keys for all other top-level roles, which may further delegate trust.
 */
class Root extends base_1.Signed {
    type = base_1.MetadataKind.Root;
    keys;
    roles;
    consistentSnapshot;
    constructor(options) {
        super(options);
        this.keys = options.keys || {};
        this.consistentSnapshot = options.consistentSnapshot ?? true;
        if (!options.roles) {
            this.roles = role_1.TOP_LEVEL_ROLE_NAMES.reduce((acc, role) => ({
                ...acc,
                [role]: new role_1.Role({ keyIDs: [], threshold: 1 }),
            }), {});
        }
        else {
            const roleNames = new Set(Object.keys(options.roles));
            if (!role_1.TOP_LEVEL_ROLE_NAMES.every((role) => roleNames.has(role))) {
                throw new error_1.ValueError('missing top-level role');
            }
            this.roles = options.roles;
        }
    }
    addKey(key, role) {
        if (!this.roles[role]) {
            throw new error_1.ValueError(`role ${role} does not exist`);
        }
        if (!this.roles[role].keyIDs.includes(key.keyID)) {
            this.roles[role].keyIDs.push(key.keyID);
        }
        this.keys[key.keyID] = key;
    }
    equals(other) {
        if (!(other instanceof Root)) {
            return false;
        }
        return (super.equals(other) &&
            this.consistentSnapshot === other.consistentSnapshot &&
            util_1.default.isDeepStrictEqual(this.keys, other.keys) &&
            util_1.default.isDeepStrictEqual(this.roles, other.roles));
    }
    toJSON() {
        return {
            _type: this.type,
            spec_version: this.specVersion,
            version: this.version,
            expires: this.expires,
            keys: keysToJSON(this.keys),
            roles: rolesToJSON(this.roles),
            consistent_snapshot: this.consistentSnapshot,
            ...this.unrecognizedFields,
        };
    }
    static fromJSON(data) {
        const { unrecognizedFields, ...commonFields } = base_1.Signed.commonFieldsFromJSON(data);
        const { keys, roles, consistent_snapshot, ...rest } = unrecognizedFields;
        if (typeof consistent_snapshot !== 'boolean') {
            throw new TypeError('consistent_snapshot must be a boolean');
        }
        return new Root({
            ...commonFields,
            keys: keysFromJSON(keys),
            roles: rolesFromJSON(roles),
            consistentSnapshot: consistent_snapshot,
            unrecognizedFields: rest,
        });
    }
}
exports.Root = Root;
function keysToJSON(keys) {
    return Object.entries(keys).reduce((acc, [keyID, key]) => ({ ...acc, [keyID]: key.toJSON() }), {});
}
function rolesToJSON(roles) {
    return Object.entries(roles).reduce((acc, [roleName, role]) => ({ ...acc, [roleName]: role.toJSON() }), {});
}
function keysFromJSON(data) {
    let keys;
    if (utils_1.guard.isDefined(data)) {
        if (!utils_1.guard.isObjectRecord(data)) {
            throw new TypeError('keys must be an object');
        }
        keys = Object.entries(data).reduce((acc, [keyID, keyData]) => ({
            ...acc,
            [keyID]: key_1.Key.fromJSON(keyID, keyData),
        }), {});
    }
    return keys;
}
function rolesFromJSON(data) {
    let roles;
    if (utils_1.guard.isDefined(data)) {
        if (!utils_1.guard.isObjectRecord(data)) {
            throw new TypeError('roles must be an object');
        }
        roles = Object.entries(data).reduce((acc, [roleName, roleData]) => ({
            ...acc,
            [roleName]: role_1.Role.fromJSON(roleData),
        }), {});
    }
    return roles;
}
