"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeCid = exports.decodeManifestCid = exports.decodeFeedCid = exports.encodeManifestReference = exports.encodeFeedReference = exports.encodeReference = exports.ReferenceType = exports.REFERENCE_HEX_LENGTH = exports.SWARM_FEED_CODEC = exports.SWARM_MANIFEST_CODEC = exports.SWARM_NS_CODEC = exports.KECCAK_256_CODEC = void 0;
const multiformats_1 = require("multiformats");
const digest_1 = require("multiformats/hashes/digest");
// https://github.com/multiformats/multicodec/blob/master/table.csv
exports.KECCAK_256_CODEC = 0x1b;
exports.SWARM_NS_CODEC = 0xe4;
exports.SWARM_MANIFEST_CODEC = 0xfa;
exports.SWARM_FEED_CODEC = 0xfb;
exports.REFERENCE_HEX_LENGTH = 64;
var ReferenceType;
(function (ReferenceType) {
    ReferenceType["FEED"] = "feed";
    ReferenceType["MANIFEST"] = "manifest";
})(ReferenceType = exports.ReferenceType || (exports.ReferenceType = {}));
/**
 * Mapping for getting ReferenceType from given codec.
 */
const CODEC_TYPE_MAPPING = {
    [exports.SWARM_FEED_CODEC]: ReferenceType.FEED,
    [exports.SWARM_MANIFEST_CODEC]: ReferenceType.MANIFEST,
};
/**
 * Encode Swarm hex-encoded Reference into CID that has appropriate codec set based on `type` parameter.
 *
 * @param ref
 * @param type
 */
function encodeReference(ref, type) {
    switch (type) {
        case ReferenceType.FEED:
            return _encodeReference(ref, exports.SWARM_FEED_CODEC);
        case ReferenceType.MANIFEST:
            return _encodeReference(ref, exports.SWARM_MANIFEST_CODEC);
        default:
            throw new Error('Unknown reference type.');
    }
}
exports.encodeReference = encodeReference;
/**
 * Encode Swarm hex-encoded Reference into CID and sets Feed codec.
 * @param ref
 */
function encodeFeedReference(ref) {
    return _encodeReference(ref, exports.SWARM_FEED_CODEC);
}
exports.encodeFeedReference = encodeFeedReference;
/**
 * Encode Swarm hex-encoded Reference into CID and sets Manifest codec.
 * @param ref
 */
function encodeManifestReference(ref) {
    return _encodeReference(ref, exports.SWARM_MANIFEST_CODEC);
}
exports.encodeManifestReference = encodeManifestReference;
/**
 * Function to decode Feed CID (both from string or CID instance) into hex encoded Swarm reference.
 *
 * @param cid
 * @throws Error if the decoded codec did not matched Swarm Feed codec
 */
function decodeFeedCid(cid) {
    const result = _decodeReference(cid);
    if (result.type !== ReferenceType.FEED) {
        throw new Error('CID did not have Swarm Feed codec!');
    }
    return result.reference;
}
exports.decodeFeedCid = decodeFeedCid;
/**
 * Function to decode Manifest CID (both from string or CID instance) into hex encoded Swarm reference.
 *
 * @param cid
 * @throws Error if the decoded codec did not matched Swarm Manifest codec
 */
function decodeManifestCid(cid) {
    const result = _decodeReference(cid);
    if (result.type !== ReferenceType.MANIFEST) {
        throw new Error('CID did not have Swarm Manifest codec!');
    }
    return result.reference;
}
exports.decodeManifestCid = decodeManifestCid;
/**
 * Decode CID or base encoded CID string into DecodeResult interface.
 * Does not throw exception if the codec was not Swarm related. In that case `type` is undefined.
 *
 * @see DecodeResult
 * @param cid
 */
function decodeCid(cid) {
    return _decodeReference(cid);
}
exports.decodeCid = decodeCid;
function _decodeReference(cid) {
    if (typeof cid === 'string') {
        cid = multiformats_1.CID.parse(cid);
    }
    return {
        reference: bytesToHex(cid.multihash.digest),
        type: CODEC_TYPE_MAPPING[cid.code],
    };
}
function _encodeReference(ref, codec) {
    assertReference(ref);
    const hashBytes = hexToBytes(ref);
    return multiformats_1.CID.createV1(codec, (0, digest_1.create)(exports.KECCAK_256_CODEC, hashBytes));
}
/**
 * Converts a hex string to Uint8Array
 *
 * @param hex string input without 0x prefix!
 */
function hexToBytes(hex) {
    assertHexString(hex);
    const bytes = new Uint8Array(hex.length / 2);
    for (let i = 0; i < bytes.length; i++) {
        const hexByte = hex.substr(i * 2, 2);
        bytes[i] = parseInt(hexByte, 16);
    }
    return bytes;
}
/**
 * Converts array of number or Uint8Array to HexString without prefix.
 *
 * @param bytes   The input array
 * @param len     The length of the non prefixed HexString
 */
function bytesToHex(bytes, len) {
    const hexByte = (n) => n.toString(16).padStart(2, '0');
    const hex = Array.from(bytes, hexByte).join('');
    if (len && hex.length !== len) {
        throw new TypeError(`Resulting HexString does not have expected length ${len}: ${hex}`);
    }
    return hex;
}
/**
 * Type guard for HexStrings.
 * Requires no 0x prefix!
 *
 * @param s string input
 * @param len expected length of the HexString
 */
function isHexString(s, len) {
    return typeof s === 'string' && /^[0-9a-f]+$/i.test(s) && (!len || s.length === len);
}
/**
 * Verifies if the provided input is a HexString.
 *
 * @param s string input
 * @param len expected length of the HexString
 * @param name optional name for the asserted value
 * @returns HexString or throws error
 */
function assertHexString(s, len, name = 'value') {
    if (!isHexString(s, len)) {
        // Don't display length error if no length specified in order not to confuse user
        const lengthMsg = len ? ` of length ${len}` : '';
        throw new TypeError(`${name} not valid hex string${lengthMsg}: ${s}`);
    }
}
function assertReference(ref) {
    assertHexString(ref);
    if (ref.length !== exports.REFERENCE_HEX_LENGTH) {
        throw new TypeError(`Reference does not have expected length 64 characters. Encrypted references are not supported.`);
    }
}
