import { ErrorResponse } from "../../request/index.js";
import { isPrimitivePartitionKeyValue } from "../../utils/typeChecks.js";
import { ChangeFeedStartFromBeginning } from "./ChangeFeedStartFromBeginning.js";
import { Constants } from "../../common/index.js";
import { ChangeFeedStartFromTime } from "./ChangeFeedStartFromTime.js";
import { QueryRange } from "../../routing/index.js";
import { FeedRangeInternal } from "./FeedRange.js";
import { hashV2PartitionKey } from "../../utils/hashing/v2.js";
import { ChangeFeedMode } from "./ChangeFeedMode.js";
/**
 * @hidden
 * Validates the change feed options passed by the user
 */
export function validateChangeFeedIteratorOptions(options) {
    if (!isChangeFeedIteratorOptions(options)) {
        throw new ErrorResponse("Invalid Changefeed Iterator Options.");
    }
    if (options?.maxItemCount && typeof options?.maxItemCount !== "number") {
        throw new ErrorResponse("maxItemCount must be number");
    }
    if (options?.maxItemCount !== undefined && options?.maxItemCount < 1) {
        throw new ErrorResponse("maxItemCount must be a positive number");
    }
}
function isChangeFeedIteratorOptions(options) {
    if (typeof options !== "object") {
        return false;
    }
    if (Object.keys(options).length === 0 && JSON.stringify(options) === "{}") {
        return true;
    }
    return options && !(isPrimitivePartitionKeyValue(options) || Array.isArray(options));
}
/**
 * @hidden
 * Checks if pkRange entirely covers the given overLapping range or there is only partial overlap.
 *
 * If no complete overlap, exact range which overlaps is retured which is used to set minEpk and maxEpk headers while quering change feed.
 */
export async function extractOverlappingRanges(epkRange, overLappingRange) {
    if (overLappingRange.minInclusive >= epkRange.min &&
        overLappingRange.maxExclusive <= epkRange.max) {
        return [undefined, undefined];
    }
    else if (overLappingRange.minInclusive <= epkRange.min &&
        overLappingRange.maxExclusive >= epkRange.max) {
        return [epkRange.min, epkRange.max];
    }
    // Right Side of overlapping range is covered
    else if (overLappingRange.minInclusive <= epkRange.min &&
        overLappingRange.maxExclusive <= epkRange.max &&
        overLappingRange.maxExclusive >= epkRange.min) {
        return [epkRange.min, overLappingRange.maxExclusive];
    }
    // Left Side of overlapping range is covered
    else {
        return [overLappingRange.minInclusive, epkRange.max];
    }
}
/**
 * @hidden
 * Checks if the object is a valid EpkRange
 */
export function isEpkRange(obj) {
    return (obj instanceof FeedRangeInternal &&
        typeof obj.minInclusive === "string" &&
        typeof obj.maxExclusive === "string" &&
        obj.minInclusive >=
            Constants.EffectivePartitionKeyConstants.MinimumInclusiveEffectivePartitionKey &&
        obj.maxExclusive <=
            Constants.EffectivePartitionKeyConstants.MaximumExclusiveEffectivePartitionKey &&
        obj.maxExclusive > obj.minInclusive);
}
/**
 * @hidden
 */
export function buildInternalChangeFeedOptions(options, continuationToken, startTime, startFromNow) {
    const internalCfOptions = {};
    internalCfOptions.maxItemCount = options?.maxItemCount;
    internalCfOptions.sessionToken = options?.sessionToken;
    internalCfOptions.continuationToken = continuationToken;
    internalCfOptions.changeFeedMode = options?.changeFeedMode;
    internalCfOptions.excludedLocations = options?.excludedLocations;
    internalCfOptions.priorityLevel = options?.priorityLevel;
    internalCfOptions.throughputBucket = options?.throughputBucket;
    // Default option of changefeed is to start from now.
    if (startFromNow) {
        internalCfOptions.startFromNow = true;
    }
    else {
        internalCfOptions.startTime = startTime;
    }
    return internalCfOptions;
}
/**
 * @hidden
 */
export function fetchStartTime(changeFeedStartFrom) {
    if (changeFeedStartFrom instanceof ChangeFeedStartFromBeginning) {
        return undefined;
    }
    else if (changeFeedStartFrom instanceof ChangeFeedStartFromTime) {
        return changeFeedStartFrom.getStartTime();
    }
}
/**
 * @hidden
 */
export function isNullOrEmpty(text) {
    return text === null || text === undefined || text.trim() === "";
}
/**
 * @hidden
 */
export async function getEPKRangeForPrefixPartitionKey(internalPartitionKey) {
    const minEPK = getEffectivePartitionKeyForMultiHashPartitioning(internalPartitionKey);
    const maxEPK = minEPK + Constants.EffectivePartitionKeyConstants.MaximumExclusiveEffectivePartitionKey;
    return new QueryRange(minEPK, maxEPK, true, false);
}
/**
 * @hidden
 */
export function getEffectivePartitionKeyForMultiHashPartitioning(partitionKeyInternal) {
    const hashArray = partitionKeyInternal.map((item) => hashV2PartitionKey([item]));
    return hashArray.join("");
}
/**
 * @hidden
 */
export async function decryptChangeFeedResponse(result, diagnosticNode, changeFeedMode, encryptionProcessor) {
    let count = 0;
    diagnosticNode.beginEncryptionDiagnostics(Constants.Encryption.DiagnosticsDecryptOperation);
    for (let item of result.result) {
        if (changeFeedMode === ChangeFeedMode.AllVersionsAndDeletes) {
            if ("current" in item && item.current !== null) {
                const { body, propertiesDecryptedCount } = await encryptionProcessor.decrypt(item.current);
                item.current = body;
                count += propertiesDecryptedCount;
            }
            if ("previous" in item && item.previous !== null) {
                const { body, propertiesDecryptedCount } = await encryptionProcessor.decrypt(item.previous);
                item.previous = body;
                count += propertiesDecryptedCount;
            }
        }
        else {
            const { body, propertiesDecryptedCount } = await encryptionProcessor.decrypt(item);
            item = body;
            count += propertiesDecryptedCount;
        }
    }
    diagnosticNode.endEncryptionDiagnostics(Constants.Encryption.DiagnosticsDecryptOperation, count);
}
/**
 * @hidden
 *  Logic for building FeedOptions from change feed iterator options
 */
export function buildFeedOptions(changeFeedOptions, continuationToken, startFromNow, startTime) {
    const feedOptions = {
        initialHeaders: {},
        useLatestVersionFeed: true,
        useAllVersionsAndDeletesFeed: false,
    };
    if (typeof changeFeedOptions.maxItemCount === "number") {
        feedOptions.maxItemCount = changeFeedOptions.maxItemCount;
    }
    if (changeFeedOptions.sessionToken) {
        feedOptions.sessionToken = changeFeedOptions.sessionToken;
    }
    if (changeFeedOptions.excludedLocations) {
        feedOptions.excludedLocations = changeFeedOptions.excludedLocations;
    }
    if (continuationToken) {
        feedOptions.accessCondition = {
            type: Constants.HttpHeaders.IfNoneMatch,
            condition: continuationToken,
        };
    }
    else if (startFromNow) {
        feedOptions.initialHeaders[Constants.HttpHeaders.IfNoneMatch] =
            Constants.ChangeFeedIfNoneMatchStartFromNowHeader;
    }
    if (startTime) {
        feedOptions.initialHeaders[Constants.HttpHeaders.IfModifiedSince] = startTime;
    }
    if (changeFeedOptions.changeFeedMode &&
        changeFeedOptions.changeFeedMode === ChangeFeedMode.AllVersionsAndDeletes) {
        feedOptions.useAllVersionsAndDeletesFeed = true;
        feedOptions.useLatestVersionFeed = false;
    }
    if (changeFeedOptions.throughputBucket) {
        feedOptions.throughputBucket = changeFeedOptions.throughputBucket;
    }
    if (changeFeedOptions.priorityLevel) {
        feedOptions.priorityLevel = changeFeedOptions.priorityLevel;
    }
    return feedOptions;
}
//# sourceMappingURL=changeFeedUtils.js.map