"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertTransactionHash = exports.makeTagUid = exports.assertAllTagsOptions = exports.assertFileData = exports.assertData = exports.assertCashoutOptions = exports.assertPostageBatchOptions = exports.assertPublicKey = exports.assertPssMessageHandler = exports.assertAddressPrefix = exports.assertTag = exports.isTag = exports.assertCollectionUploadOptions = exports.assertFileUploadOptions = exports.assertUploadOptions = exports.assertRequestOptions = exports.assertBatchId = exports.assertAddress = exports.addCidConversionFunction = exports.makeReferenceOrEns = exports.assertReferenceOrEns = exports.assertReference = exports.assertPositiveInteger = exports.assertNonNegativeInteger = exports.assertInteger = exports.assertBoolean = exports.assertStrictlyObject = exports.isError = exports.isStrictlyObject = exports.isObject = exports.isInteger = exports.isUint8Array = void 0;
const types_1 = require("../types");
const error_1 = require("./error");
const file_1 = require("./file");
const hex_1 = require("./hex");
const stream_1 = require("./stream");
const swarm_cid_1 = require("@ethersphere/swarm-cid");
function isUint8Array(obj) {
    return obj instanceof Uint8Array;
}
exports.isUint8Array = isUint8Array;
function isInteger(value) {
    return ((typeof value === 'string' && /^-?(0|[1-9][0-9]*)$/g.test(value)) ||
        (typeof value === 'number' &&
            value > Number.MIN_SAFE_INTEGER &&
            value < Number.MAX_SAFE_INTEGER &&
            Number.isInteger(value)));
}
exports.isInteger = isInteger;
function isObject(value) {
    return value !== null && typeof value === 'object';
}
exports.isObject = isObject;
/**
 * Generally it is discouraged to use `object` type, but in this case I think
 * it is best to do so as it is possible to easily convert from `object`to other
 * types, which will be usually the case after asserting that the object is
 * strictly object. With for example Record<string, unknown> you have to first
 * cast it to `unknown` which I think bit defeat the purpose.
 *
 * @param value
 */
// eslint-disable-next-line @typescript-eslint/ban-types
function isStrictlyObject(value) {
    return isObject(value) && !Array.isArray(value);
}
exports.isStrictlyObject = isStrictlyObject;
/**
 * Asserts if object is Error
 *
 * @param e
 */
function isError(e) {
    return e instanceof Error;
}
exports.isError = isError;
// eslint-disable-next-line @typescript-eslint/ban-types
function assertStrictlyObject(value, name = 'value') {
    if (!isStrictlyObject(value)) {
        throw new TypeError(`${name} has to be an object that is not null nor array!`);
    }
}
exports.assertStrictlyObject = assertStrictlyObject;
function assertBoolean(value, name = 'value') {
    if (value !== true && value !== false)
        throw new TypeError(`${name} is not boolean`);
}
exports.assertBoolean = assertBoolean;
function assertInteger(value, name = 'value') {
    if (!isInteger(value))
        throw new TypeError(`${name} is not integer`);
}
exports.assertInteger = assertInteger;
function assertNonNegativeInteger(value, name = 'Value') {
    assertInteger(value, name);
    if (Number(value) < 0)
        throw new error_1.BeeArgumentError(`${name} has to be bigger or equal to zero`, value);
}
exports.assertNonNegativeInteger = assertNonNegativeInteger;
function assertPositiveInteger(value, name = 'Value') {
    assertInteger(value, name);
    if (Number(value) <= 0)
        throw new error_1.BeeArgumentError(`${name} has to be bigger then zero`, value);
}
exports.assertPositiveInteger = assertPositiveInteger;
function assertReference(value) {
    try {
        (0, hex_1.assertHexString)(value, types_1.REFERENCE_HEX_LENGTH);
    }
    catch (e) {
        (0, hex_1.assertHexString)(value, types_1.ENCRYPTED_REFERENCE_HEX_LENGTH);
    }
}
exports.assertReference = assertReference;
function assertReferenceOrEns(value) {
    if (typeof value !== 'string') {
        throw new TypeError('ReferenceOrEns has to be a string!');
    }
    if ((0, hex_1.isHexString)(value)) {
        assertReference(value);
        return;
    }
    /**
     * a.asdf - VALID
     * test.eth - VALID
     * ADAM.ETH - VALID
     * ADAM UHLIR.ETH - INVALID
     * test.whatever.eth - VALID
     * -adg.ets - INVALID
     * adg-.ets - INVALID
     * as-a.com - VALID
     * ethswarm.org - VALID
     * http://asdf.asf - INVALID
     * řš+ýí.šě+ř.čě - VALID
     * tsg.asg?asg - INVALID
     * tsg.asg:1599 - INVALID
     * ethswarm.something- - INVALID
     * ethswarm.-something - INVALID
     * ethswarm.some-thing - VALID
     *
     * The idea of this regex is to match strings that are 1 to 63 characters long and do not start or end with dash character
     *
     * This part matches 2-63 character string that does not start or end with -
     * [^-.\/?:\s][^.\/?:\s]{0,61}[^-.\/?:\s]   <regexp1>
     *
     * For 1 character long string we use the part after |
     * [^-.\/?:\s]   <regexp2>
     *
     * This is terminated in a group with . character an repeated at least once
     * (<regexp1>|<regexp2>\.)+
     *
     * This covers everything but top level domain which is 2 to 63 characters long so we can just use the <regexp2> again
     * ^(<regexp1>|<regexp2>\.)+<regexp1>$
     */
    const DOMAIN_REGEX = /^(?:(?:[^-.\/?:\s][^.\/?:\s]{0,61}[^-.\/?:\s]|[^-.\/?:\s]{1,2})\.)+[^-.\/?:\s][^.\/?:\s]{0,61}[^-.\/?:\s]$/;
    // We are doing best-effort validation of domain here. The proper way would be to do validation using IDNA UTS64 standard
    // but that would give us high penalty to our dependencies as the library (idna-uts46-hx) that does this validation and translation
    // adds 160kB minified size which is significant. We expects that full validation will be done on Bee side.
    if (!DOMAIN_REGEX.test(value)) {
        throw new TypeError('ReferenceOrEns is not valid Reference, but also not valid ENS domain.');
    }
}
exports.assertReferenceOrEns = assertReferenceOrEns;
/**
 * Function that mainly converts Swarm CID into hex encoded Swarm Reference
 *
 * @param value
 * @param expectedCidType
 */
function makeReferenceOrEns(value, expectedCidType) {
    var _a;
    if (typeof value !== 'string') {
        throw new TypeError('ReferenceCidOrEns has to be a string!');
    }
    try {
        const result = (0, swarm_cid_1.decodeCid)(value);
        if (result.type !== expectedCidType) {
            throw new error_1.BeeError(`CID was expected to be of type ${expectedCidType}, but got instead ${(_a = result.type) !== null && _a !== void 0 ? _a : 'non-Swarm CID'}`);
        }
        return result.reference;
    }
    catch (e) {
        if (e instanceof error_1.BeeError)
            throw e;
    }
    assertReferenceOrEns(value);
    return value;
}
exports.makeReferenceOrEns = makeReferenceOrEns;
/**
 * Function that adds getter which converts the reference into CID base32 encoded string.
 * @param result
 * @param cidType Type as described in the @ethersphere/swarm-cids-js -> ReferenceType
 */
function addCidConversionFunction(result, cidType) {
    return Object.assign(Object.assign({}, result), { get cid() {
            return (0, swarm_cid_1.encodeReference)(result.reference, cidType).toString();
        } });
}
exports.addCidConversionFunction = addCidConversionFunction;
function assertAddress(value) {
    (0, hex_1.assertHexString)(value, types_1.ADDRESS_HEX_LENGTH, 'Address');
}
exports.assertAddress = assertAddress;
function assertBatchId(value) {
    (0, hex_1.assertHexString)(value, types_1.BATCH_ID_HEX_LENGTH, 'BatchId');
}
exports.assertBatchId = assertBatchId;
function assertRequestOptions(value, name = 'RequestOptions') {
    if (value === undefined) {
        return;
    }
    if (!isStrictlyObject(value)) {
        throw new TypeError(`${name} has to be an object!`);
    }
    const options = value;
    if (options.retry) {
        assertNonNegativeInteger(options.retry, `${name}.retry`);
    }
    if (options.timeout) {
        assertNonNegativeInteger(options.timeout, `${name}.timeout`);
    }
    if (options.fetch && typeof options.fetch !== 'function') {
        throw new TypeError(`${name}.fetch has to be a function or undefined!`);
    }
}
exports.assertRequestOptions = assertRequestOptions;
function assertUploadOptions(value, name = 'UploadOptions') {
    if (!isStrictlyObject(value)) {
        throw new TypeError(`${name} has to be an object!`);
    }
    assertRequestOptions(value, name);
    const options = value;
    if (options.pin && typeof options.pin !== 'boolean') {
        throw new TypeError(`options.pin property in ${name} has to be boolean or undefined!`);
    }
    if (options.encrypt && typeof options.encrypt !== 'boolean') {
        throw new TypeError(`options.encrypt property in ${name} has to be boolean or undefined!`);
    }
    if (options.tag) {
        if (typeof options.tag !== 'number') {
            throw new TypeError(`options.tag property in ${name} has to be number or undefined!`);
        }
        assertNonNegativeInteger(options.tag, 'options.tag');
    }
}
exports.assertUploadOptions = assertUploadOptions;
function assertFileUploadOptions(value) {
    assertUploadOptions(value, 'FileUploadOptions');
    const options = value;
    if (options.size) {
        if (typeof options.size !== 'number') {
            throw new TypeError('tag property in FileUploadOptions has to be number or undefined!');
        }
        assertNonNegativeInteger(options.size, 'options.size');
    }
    if (options.contentType && typeof options.contentType !== 'string') {
        throw new TypeError('contentType property in FileUploadOptions has to be string or undefined!');
    }
}
exports.assertFileUploadOptions = assertFileUploadOptions;
function assertCollectionUploadOptions(value) {
    assertUploadOptions(value, 'CollectionUploadOptions');
    const options = value;
    if (options.indexDocument && typeof options.indexDocument !== 'string') {
        throw new TypeError('indexDocument property in CollectionUploadOptions has to be string or undefined!');
    }
    if (options.errorDocument && typeof options.errorDocument !== 'string') {
        throw new TypeError('errorDocument property in CollectionUploadOptions has to be string or undefined!');
    }
}
exports.assertCollectionUploadOptions = assertCollectionUploadOptions;
function isTag(value) {
    if (!isStrictlyObject(value)) {
        return false;
    }
    const tag = value;
    const numberProperties = ['total', 'processed', 'synced', 'uid'];
    const correctNumberProperties = numberProperties.every(numberProperty => typeof tag[numberProperty] === 'number');
    if (!correctNumberProperties || !tag.startedAt || typeof tag.startedAt !== 'string') {
        return false;
    }
    return true;
}
exports.isTag = isTag;
function assertTag(value) {
    if (!isStrictlyObject(value)) {
        throw new TypeError('Tag is not an object!');
    }
    const tag = value;
    const numberProperties = ['total', 'processed', 'synced', 'uid'];
    for (const numberProperty of numberProperties) {
        if (!tag[numberProperty]) {
            throw new TypeError(`Tag's property '${numberProperty}' has to be specified!`);
        }
        if (typeof tag[numberProperty] !== 'number') {
            throw new TypeError(`Tag's property '${numberProperty}' has to be number!`);
        }
    }
    if (!tag.startedAt) {
        throw new TypeError("Tag's property 'startedAt' has to be specified!");
    }
    if (typeof tag.startedAt !== 'string') {
        throw new TypeError("Tag's property 'startedAt' has to be string!");
    }
}
exports.assertTag = assertTag;
function assertAddressPrefix(value) {
    (0, hex_1.assertHexString)(value, undefined, 'AddressPrefix');
    if (value.length > types_1.PSS_TARGET_HEX_LENGTH_MAX) {
        throw new error_1.BeeArgumentError(`AddressPrefix must have length of ${types_1.PSS_TARGET_HEX_LENGTH_MAX} at most! Got string with ${value.length}`, value);
    }
}
exports.assertAddressPrefix = assertAddressPrefix;
function assertPssMessageHandler(value) {
    if (!isStrictlyObject(value)) {
        throw new TypeError('PssMessageHandler has to be object!');
    }
    const handler = value;
    if (typeof handler.onMessage !== 'function') {
        throw new TypeError('onMessage property of PssMessageHandler has to be function!');
    }
    if (typeof handler.onError !== 'function') {
        throw new TypeError('onError property of PssMessageHandler has to be function!');
    }
}
exports.assertPssMessageHandler = assertPssMessageHandler;
function assertPublicKey(value) {
    (0, hex_1.assertHexString)(value, types_1.PUBKEY_HEX_LENGTH, 'PublicKey');
}
exports.assertPublicKey = assertPublicKey;
function assertPostageBatchOptions(value) {
    if (value === undefined) {
        return;
    }
    assertStrictlyObject(value);
    const options = value;
    assertRequestOptions(options, 'PostageBatchOptions');
    if (options === null || options === void 0 ? void 0 : options.gasPrice) {
        assertNonNegativeInteger(options.gasPrice);
    }
    if ((options === null || options === void 0 ? void 0 : options.immutableFlag) !== undefined) {
        assertBoolean(options.immutableFlag);
    }
    if ((options === null || options === void 0 ? void 0 : options.waitForUsable) !== undefined) {
        assertBoolean(options.waitForUsable);
    }
    if ((options === null || options === void 0 ? void 0 : options.waitForUsableTimeout) !== undefined) {
        assertNonNegativeInteger(options.waitForUsableTimeout, 'options.waitForUsableTimeout');
    }
}
exports.assertPostageBatchOptions = assertPostageBatchOptions;
function assertCashoutOptions(value) {
    if (value === undefined) {
        return;
    }
    assertStrictlyObject(value);
    const options = value;
    assertRequestOptions(options, 'PostageBatchOptions');
    if (options === null || options === void 0 ? void 0 : options.gasLimit) {
        assertNonNegativeInteger(options.gasLimit);
    }
    if (options === null || options === void 0 ? void 0 : options.gasPrice) {
        assertNonNegativeInteger(options.gasPrice);
    }
}
exports.assertCashoutOptions = assertCashoutOptions;
/**
 * Check whether the given parameter is valid data to upload
 * @param value
 * @throws TypeError if not valid
 */
function assertData(value) {
    if (typeof value !== 'string' && !(value instanceof Uint8Array)) {
        throw new TypeError('Data must be either string or Uint8Array!');
    }
}
exports.assertData = assertData;
/**
 * Check whether the given parameter is a correct file representation to file upload.
 * @param value
 * @throws TypeError if not valid
 */
function assertFileData(value) {
    if (typeof value !== 'string' && !(value instanceof Uint8Array) && !(0, file_1.isFile)(value) && !(0, stream_1.isReadable)(value)) {
        throw new TypeError('Data must be either string, Readable, Uint8Array or File!');
    }
}
exports.assertFileData = assertFileData;
/**
 * Checks whether optional options for AllTags query are valid
 * @param options
 */
function assertAllTagsOptions(entry) {
    if (entry !== undefined && !isStrictlyObject(entry)) {
        throw new TypeError('options has to be an object or undefined!');
    }
    assertRequestOptions(entry, 'AllTagsOptions');
    const options = entry;
    if ((options === null || options === void 0 ? void 0 : options.limit) !== undefined) {
        if (typeof options.limit !== 'number') {
            throw new TypeError('AllTagsOptions.limit has to be a number or undefined!');
        }
        if (options.limit < types_1.TAGS_LIMIT_MIN) {
            throw new error_1.BeeArgumentError(`AllTagsOptions.limit has to be at least ${types_1.TAGS_LIMIT_MIN}`, options.limit);
        }
        if (options.limit > types_1.TAGS_LIMIT_MAX) {
            throw new error_1.BeeArgumentError(`AllTagsOptions.limit has to be at most ${types_1.TAGS_LIMIT_MAX}`, options.limit);
        }
    }
    if ((options === null || options === void 0 ? void 0 : options.offset) !== undefined) {
        assertNonNegativeInteger(options.offset, 'AllTagsOptions.offset');
    }
}
exports.assertAllTagsOptions = assertAllTagsOptions;
/**
 * Utility functions that return Tag UID
 * @param tagUid
 */
function makeTagUid(tagUid) {
    if (tagUid === undefined || tagUid === null) {
        throw new TypeError('TagUid was expected but got undefined or null instead!');
    }
    if (isTag(tagUid)) {
        return tagUid.uid;
    }
    else if (typeof tagUid === 'number') {
        assertNonNegativeInteger(tagUid, 'UID');
        return tagUid;
    }
    else if (typeof tagUid === 'string') {
        const int = parseInt(tagUid);
        if (isNaN(int)) {
            throw new TypeError('Passed tagUid string is not valid integer!');
        }
        if (int < 0) {
            throw new TypeError(`TagUid was expected to be positive non-negative integer! Got ${int}`);
        }
        return int;
    }
    throw new TypeError('tagUid has to be either Tag or a number (UID)!');
}
exports.makeTagUid = makeTagUid;
function assertTransactionHash(transactionHash) {
    if (typeof transactionHash !== 'string') {
        throw new TypeError('TransactionHash has to be a string!');
    }
    (0, hex_1.assertPrefixedHexString)(transactionHash, 'TransactionHash');
    // Hash is 64 long + '0x' prefix = 66
    if (transactionHash.length !== 66) {
        throw new TypeError('TransactionHash has to be prefixed hex string with total length 66 (prefix including)');
    }
}
exports.assertTransactionHash = assertTransactionHash;
