"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseContinuationTokenManager = void 0;
const CompositeQueryContinuationToken_js_1 = require("../../documents/ContinuationToken/CompositeQueryContinuationToken.js");
const PartitionRangeManager_js_1 = require("../PartitionRangeManager.js");
/**
 * Base abstract class for continuation token management.
 * Provides common functionality shared between parallel and ORDER BY query token managers.
 * @internal
 */
class BaseContinuationTokenManager {
    ranges = [];
    partitionRangeManager = new PartitionRangeManager_js_1.PartitionRangeManager();
    constructor(initialContinuationToken) {
        if (initialContinuationToken) {
            const token = (0, CompositeQueryContinuationToken_js_1.parseBaseContinuationToken)(initialContinuationToken);
            if (token?.rangeMappings) {
                this.ranges = token.rangeMappings;
            }
        }
    }
    /**
     * Provides controlled access to partition range manager for subclasses.
     * This is the only protected access point needed.
     */
    get partitionManager() {
        return this.partitionRangeManager;
    }
    /**
     * Provides controlled access to ranges for subclasses.
     * Made protected to allow subclass range management.
     */
    get rangeList() {
        return this.ranges;
    }
    /**
     * Processes query results and generates continuation tokens for pagination.
     * Handles response data processing, range management, and token generation.
     *
     * @param pageSize - Maximum number of items to return in this page
     * @param isResponseEmpty - Whether the current response contains no data
     * @param responseResult - Optional response data containing partition mappings and query-specific data
     * @returns Object containing the end index for slicing results and optional continuation token for next page
     */
    paginateResults(pageSize, isResponseEmpty, responseResult) {
        // Process response data
        if (responseResult) {
            this.processResponseResult(responseResult);
        }
        this.removeExhaustedRangesFromRanges();
        const result = this.processRangesForPagination(pageSize, isResponseEmpty);
        const tokenString = this.generateContinuationTokenString();
        // Clean up processed ranges
        this.trimProcessedData(result.processedRanges, result.endIndex);
        return {
            endIndex: result.endIndex,
            continuationToken: tokenString,
        };
    }
    /**
     * Generates continuation token string using the appropriate serialization function.
     * This provides a common implementation that delegates to query-specific logic.
     */
    generateContinuationTokenString() {
        const token = this.getCurrentContinuationToken();
        if (!token) {
            return undefined;
        }
        const serializeFunction = this.getSerializationFunction();
        return serializeFunction(token);
    }
    /**
     * Cleans up processed data after a page has been returned.
     * Handles both common and query-specific cleanup.
     */
    trimProcessedData(processedRanges, endIndex) {
        processedRanges.forEach((rangeId) => {
            this.partitionRangeManager.removePartitionRangeMapping(rangeId);
        });
        // Delegate query-specific cleanup to subclass
        this.performQuerySpecificDataTrim(processedRanges, endIndex);
    }
    addPartitionKeyRangeMap(partitionKeyRangeMap) {
        this.partitionRangeManager.addPartitionKeyRangeMap(partitionKeyRangeMap);
    }
    /**
     * Processes the entire response result and updates the continuation token manager state.
     * This encapsulates all response handling logic in one place.
     */
    processResponseResult(responseResult) {
        // Handle partition key range map
        if (responseResult.partitionKeyRangeMap) {
            this.addPartitionKeyRangeMap(responseResult.partitionKeyRangeMap);
        }
        // Handle partition range updates
        if (responseResult.updatedContinuationRanges) {
            this.handlePartitionRangeChanges(responseResult.updatedContinuationRanges);
        }
        this.processQuerySpecificResponse(responseResult);
    }
    isPartitionExhausted(continuationToken) {
        return (!continuationToken ||
            continuationToken === "" ||
            continuationToken === "null" ||
            continuationToken.toLowerCase() === "null");
    }
    removeExhaustedRangesFromRanges() {
        if (!this.ranges || !Array.isArray(this.ranges)) {
            return;
        }
        this.ranges = this.ranges.filter((mapping) => {
            if (!mapping) {
                return false;
            }
            const isExhausted = this.isPartitionExhausted(mapping.continuationToken);
            return !isExhausted;
        });
    }
    handlePartitionRangeChanges(updatedContinuationRanges) {
        if (updatedContinuationRanges && Object.keys(updatedContinuationRanges).length === 0) {
            return;
        }
        Object.entries(updatedContinuationRanges).forEach(([rangeKey, rangeChange]) => {
            this.processRangeChange(rangeKey, rangeChange);
        });
    }
    processRangeChange(_rangeKey, rangeChange) {
        const { oldRange, newRanges, continuationToken } = rangeChange;
        if (newRanges.length === 1) {
            this.handleRangeMerge(oldRange, newRanges[0], continuationToken);
        }
        else {
            this.handleRangeSplit(oldRange, newRanges, continuationToken);
        }
    }
    handleRangeMerge(oldRange, newRange, continuationToken) {
        // Find existing range mapping to update in the common ranges array
        const existingMappingIndex = this.ranges.findIndex((mapping) => mapping.queryRange.min === oldRange.min && mapping.queryRange.max === oldRange.max);
        if (existingMappingIndex < 0) {
            return;
        }
        // Update existing mapping with new range properties
        const existingMapping = this.ranges[existingMappingIndex];
        // Create new simplified QueryRange with updated boundaries
        const updatedQueryRange = {
            min: newRange.min,
            max: newRange.max,
        };
        // Update the mapping
        existingMapping.queryRange = updatedQueryRange;
        existingMapping.continuationToken = continuationToken;
    }
    handleRangeSplit(oldRange, newRanges, continuationToken) {
        // Remove the old range mapping from the common ranges array
        this.ranges = this.ranges.filter((mapping) => !(mapping.queryRange.min === oldRange.min && mapping.queryRange.max === oldRange.max));
        // Add new range mappings for each split range
        newRanges.forEach((newRange) => {
            this.createNewRangeMapping(newRange, continuationToken);
        });
    }
    createNewRangeMapping(partitionKeyRange, continuationToken) {
        // Create new simplified QueryRange
        const queryRange = {
            min: partitionKeyRange.min,
            max: partitionKeyRange.max,
        };
        // Create new QueryRangeWithContinuationToken
        const newRangeWithToken = {
            queryRange: queryRange,
            continuationToken: continuationToken,
        };
        this.ranges.push(newRangeWithToken);
    }
}
exports.BaseContinuationTokenManager = BaseContinuationTokenManager;
//# sourceMappingURL=BaseContinuationTokenManager.js.map