"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetricsManager = exports.metrics = void 0;
const errors_js_1 = require("../../errors.js");
const index_js_1 = require("../../graphql/index.js");
const index_js_2 = require("../../index.js");
const index_js_3 = require("../deserialize/index.js");
const utils_js_1 = require("../query/utils.js");
const index_js_4 = require("../serialize/index.js");
const metrics = () => {
    return {
        aggregate: (property) => new MetricsManager(property),
    };
};
exports.metrics = metrics;
class MetricsManager {
    propertyName;
    constructor(property) {
        this.propertyName = property;
    }
    map(metrics) {
        const out = {};
        metrics.forEach((metric) => {
            out[metric] = true;
        });
        return out;
    }
    /**
     * Define the metrics to be returned for a BOOL or BOOL_ARRAY property when aggregating over a collection.
     *
     * If none of the arguments are provided then all metrics will be returned.
     *
     * @param {('count' | 'percentageFalse' | 'percentageTrue' | 'totalFalse' | 'totalTrue')[]} metrics The metrics to return.
     * @returns {MetricsBoolean<P>} The metrics for the property.
     */
    boolean(metrics) {
        if (metrics === undefined || metrics.length === 0) {
            metrics = ['count', 'percentageFalse', 'percentageTrue', 'totalFalse', 'totalTrue'];
        }
        return {
            ...this.map(metrics),
            kind: 'boolean',
            propertyName: this.propertyName,
        };
    }
    /**
     * Define the metrics to be returned for a DATE or DATE_ARRAY property when aggregating over a collection.
     *
     * If none of the arguments are provided then all metrics will be returned.
     *
     * @param {('count' | 'maximum' | 'median' | 'minimum' | 'mode')[]} metrics The metrics to return.
     * @returns {MetricsDate<P>} The metrics for the property.
     */
    date(metrics) {
        if (metrics === undefined || metrics.length === 0) {
            metrics = ['count', 'maximum', 'median', 'minimum', 'mode'];
        }
        return {
            ...this.map(metrics),
            kind: 'date',
            propertyName: this.propertyName,
        };
    }
    /**
     * Define the metrics to be returned for an INT or INT_ARRAY property when aggregating over a collection.
     *
     * If none of the arguments are provided then all metrics will be returned.
     *
     * @param {('count' | 'maximum' | 'mean' | 'median' | 'minimum' | 'mode' | 'sum')[]} metrics The metrics to return.
     * @returns {MetricsInteger<P>} The metrics for the property.
     */
    integer(metrics) {
        if (metrics === undefined || metrics.length === 0) {
            metrics = ['count', 'maximum', 'mean', 'median', 'minimum', 'mode', 'sum'];
        }
        return {
            ...this.map(metrics),
            kind: 'integer',
            propertyName: this.propertyName,
        };
    }
    /**
     * Define the metrics to be returned for a NUMBER or NUMBER_ARRAY property when aggregating over a collection.
     *
     * If none of the arguments are provided then all metrics will be returned.
     *
     * @param {('count' | 'maximum' | 'mean' | 'median' | 'minimum' | 'mode' | 'sum')[]} metrics The metrics to return.
     * @returns {MetricsNumber<P>} The metrics for the property.
     */
    number(metrics) {
        if (metrics === undefined || metrics.length === 0) {
            metrics = ['count', 'maximum', 'mean', 'median', 'minimum', 'mode', 'sum'];
        }
        return {
            ...this.map(metrics),
            kind: 'number',
            propertyName: this.propertyName,
        };
    }
    // public reference(metrics: 'pointingTo'[]): MetricsReference<T> {
    //   return {
    //     ...this.map(metrics),
    //     kind: 'reference',
    //     propertyName: this.propertyName,
    //   };
    // }
    /**
     * Define the metrics to be returned for a TEXT or TEXT_ARRAY property when aggregating over a collection.
     *
     * If none of the arguments are provided then all metrics will be returned.
     *
     * @param {('count' | 'topOccurrencesOccurs' | 'topOccurrencesValue')[]} metrics The metrics to return.
     * @param {number} [minOccurrences] The how many top occurrences to return.
     * @returns {MetricsText<P>} The metrics for the property.
     */
    text(metrics, minOccurrences) {
        if (metrics === undefined || metrics.length === 0) {
            metrics = ['count', 'topOccurrencesOccurs', 'topOccurrencesValue'];
        }
        return {
            count: metrics.includes('count'),
            topOccurrences: metrics.includes('topOccurrencesOccurs') || metrics.includes('topOccurrencesValue')
                ? {
                    occurs: metrics.includes('topOccurrencesOccurs'),
                    value: metrics.includes('topOccurrencesValue'),
                }
                : undefined,
            minOccurrences,
            kind: 'text',
            propertyName: this.propertyName,
        };
    }
}
exports.MetricsManager = MetricsManager;
class AggregateManager {
    connection;
    groupBy;
    name;
    dbVersionSupport;
    consistencyLevel;
    tenant;
    grpcChecker;
    constructor(connection, name, dbVersionSupport, consistencyLevel, tenant) {
        this.connection = connection;
        this.name = name;
        this.dbVersionSupport = dbVersionSupport;
        this.consistencyLevel = consistencyLevel;
        this.tenant = tenant;
        this.grpcChecker = this.dbVersionSupport.supportsAggregateGRPC().then((res) => res.supports);
        this.groupBy = {
            hybrid: async (query, opts) => {
                if (await this.grpcChecker) {
                    const group = typeof opts.groupBy === 'string' ? { property: opts.groupBy } : opts.groupBy;
                    return this.grpc()
                        .then(async (aggregate) => aggregate.withHybrid({
                        ...(await index_js_4.Serialize.aggregate.hybrid(query, opts)),
                        groupBy: index_js_4.Serialize.aggregate.groupBy(group),
                        limit: group.limit,
                    }))
                        .then((reply) => index_js_3.Deserialize.aggregateGroupBy(reply));
                }
                let builder = this.base(opts?.returnMetrics, opts?.filters, opts?.groupBy).withHybrid({
                    query: query,
                    alpha: opts?.alpha,
                    maxVectorDistance: opts?.maxVectorDistance,
                    properties: opts?.queryProperties,
                    targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
                    vector: opts?.vector,
                });
                if (opts?.objectLimit) {
                    builder = builder.withObjectLimit(opts.objectLimit);
                }
                return this.doGroupBy(builder);
            },
            nearImage: async (image, opts) => {
                const [b64, usesGrpc] = await Promise.all([await (0, index_js_2.toBase64FromMedia)(image), await this.grpcChecker]);
                if (usesGrpc) {
                    const group = typeof opts.groupBy === 'string' ? { property: opts.groupBy } : opts.groupBy;
                    return this.grpc()
                        .then((aggregate) => aggregate.withNearImage({
                        ...index_js_4.Serialize.aggregate.nearImage(b64, opts),
                        groupBy: index_js_4.Serialize.aggregate.groupBy(group),
                        limit: group.limit,
                    }))
                        .then((reply) => index_js_3.Deserialize.aggregateGroupBy(reply));
                }
                const builder = this.base(opts?.returnMetrics, opts?.filters, opts?.groupBy).withNearImage({
                    image: b64,
                    certainty: opts?.certainty,
                    distance: opts?.distance,
                    targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
                });
                if (opts?.objectLimit) {
                    builder.withObjectLimit(opts?.objectLimit);
                }
                return this.doGroupBy(builder);
            },
            nearObject: async (id, opts) => {
                if (await this.grpcChecker) {
                    const group = typeof opts.groupBy === 'string' ? { property: opts.groupBy } : opts.groupBy;
                    return this.grpc()
                        .then((aggregate) => aggregate.withNearObject({
                        ...index_js_4.Serialize.aggregate.nearObject(id, opts),
                        groupBy: index_js_4.Serialize.aggregate.groupBy(group),
                        limit: group.limit,
                    }))
                        .then((reply) => index_js_3.Deserialize.aggregateGroupBy(reply));
                }
                const builder = this.base(opts?.returnMetrics, opts?.filters, opts?.groupBy).withNearObject({
                    id: id,
                    certainty: opts?.certainty,
                    distance: opts?.distance,
                    targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
                });
                if (opts?.objectLimit) {
                    builder.withObjectLimit(opts.objectLimit);
                }
                return this.doGroupBy(builder);
            },
            nearText: async (query, opts) => {
                if (await this.grpcChecker) {
                    const group = typeof opts.groupBy === 'string' ? { property: opts.groupBy } : opts.groupBy;
                    return this.grpc()
                        .then((aggregate) => aggregate.withNearText({
                        ...index_js_4.Serialize.aggregate.nearText(query, opts),
                        groupBy: index_js_4.Serialize.aggregate.groupBy(group),
                        limit: group.limit,
                    }))
                        .then((reply) => index_js_3.Deserialize.aggregateGroupBy(reply));
                }
                const builder = this.base(opts?.returnMetrics, opts?.filters, opts?.groupBy).withNearText({
                    concepts: Array.isArray(query) ? query : [query],
                    certainty: opts?.certainty,
                    distance: opts?.distance,
                    targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
                });
                if (opts?.objectLimit) {
                    builder.withObjectLimit(opts.objectLimit);
                }
                return this.doGroupBy(builder);
            },
            nearVector: async (vector, opts) => {
                if (await this.grpcChecker) {
                    const group = typeof opts.groupBy === 'string' ? { property: opts.groupBy } : opts.groupBy;
                    return this.grpc()
                        .then(async (aggregate) => aggregate.withNearVector({
                        ...(await index_js_4.Serialize.aggregate.nearVector(vector, opts)),
                        groupBy: index_js_4.Serialize.aggregate.groupBy(group),
                        limit: group.limit,
                    }))
                        .then((reply) => index_js_3.Deserialize.aggregateGroupBy(reply));
                }
                const builder = this.base(opts?.returnMetrics, opts?.filters, opts?.groupBy).withNearVector({
                    vector: vector,
                    certainty: opts?.certainty,
                    distance: opts?.distance,
                    targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
                });
                if (opts?.objectLimit) {
                    builder.withObjectLimit(opts.objectLimit);
                }
                return this.doGroupBy(builder);
            },
            overAll: async (opts) => {
                if (await this.grpcChecker) {
                    const group = typeof opts.groupBy === 'string' ? { property: opts.groupBy } : opts.groupBy;
                    return this.grpc()
                        .then((aggregate) => aggregate.withFetch({
                        ...index_js_4.Serialize.aggregate.overAll(opts),
                        groupBy: index_js_4.Serialize.aggregate.groupBy(group),
                        limit: group.limit,
                    }))
                        .then((reply) => index_js_3.Deserialize.aggregateGroupBy(reply));
                }
                const builder = this.base(opts?.returnMetrics, opts?.filters, opts?.groupBy);
                return this.doGroupBy(builder);
            },
        };
    }
    grpc = () => this.connection.aggregate(this.name, this.consistencyLevel, this.tenant);
    gql() {
        return new index_js_1.Aggregator(this.connection);
    }
    base(metrics, filters, groupBy) {
        let fields = 'meta { count }';
        let builder = this.gql().withClassName(this.name);
        if (metrics) {
            if (Array.isArray(metrics)) {
                fields += metrics.map((m) => this.metrics(m)).join(' ');
            }
            else {
                fields += this.metrics(metrics);
            }
        }
        if (groupBy) {
            builder = builder.withGroupBy(typeof groupBy === 'string' ? [groupBy] : [groupBy.property]);
            fields += 'groupedBy { path value }';
            if (typeof groupBy !== 'string' && groupBy?.limit) {
                builder = builder.withLimit(groupBy.limit);
            }
        }
        if (fields !== '') {
            builder = builder.withFields(fields);
        }
        if (filters) {
            builder = builder.withWhere(index_js_4.Serialize.filtersREST(filters));
        }
        if (this.tenant) {
            builder = builder.withTenant(this.tenant);
        }
        return builder;
    }
    metrics(metrics) {
        let body = '';
        const { kind, propertyName, ...rest } = metrics;
        switch (kind) {
            case 'text': {
                const { minOccurrences, ...restText } = rest;
                body = Object.entries(restText)
                    .map(([key, value]) => {
                    if (value) {
                        return value instanceof Object
                            ? `topOccurrences${minOccurrences ? `(limit: ${minOccurrences})` : ''} { ${value.occurs ? 'occurs' : ''} ${value.value ? 'value' : ''} }`
                            : key;
                    }
                })
                    .join(' ');
                break;
            }
            default:
                body = Object.entries(rest)
                    .map(([key, value]) => (value ? key : ''))
                    .join(' ');
        }
        return `${propertyName} { ${body} }`;
    }
    static use(connection, name, dbVersionSupport, consistencyLevel, tenant) {
        return new AggregateManager(connection, name, dbVersionSupport, consistencyLevel, tenant);
    }
    async hybrid(query, opts) {
        if (await this.grpcChecker) {
            return this.grpc()
                .then(async (aggregate) => aggregate.withHybrid(await index_js_4.Serialize.aggregate.hybrid(query, opts)))
                .then((reply) => index_js_3.Deserialize.aggregate(reply));
        }
        let builder = this.base(opts?.returnMetrics, opts?.filters).withHybrid({
            query: query,
            alpha: opts?.alpha,
            maxVectorDistance: opts?.maxVectorDistance,
            properties: opts?.queryProperties,
            targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
            vector: opts?.vector,
        });
        if (opts?.objectLimit) {
            builder = builder.withObjectLimit(opts.objectLimit);
        }
        return this.do(builder);
    }
    async nearImage(image, opts) {
        const [b64, usesGrpc] = await Promise.all([await (0, index_js_2.toBase64FromMedia)(image), await this.grpcChecker]);
        if (usesGrpc) {
            return this.grpc()
                .then((aggregate) => aggregate.withNearImage(index_js_4.Serialize.aggregate.nearImage(b64, opts)))
                .then((reply) => index_js_3.Deserialize.aggregate(reply));
        }
        const builder = this.base(opts?.returnMetrics, opts?.filters).withNearImage({
            image: b64,
            certainty: opts?.certainty,
            distance: opts?.distance,
            targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
        });
        if (opts?.objectLimit) {
            builder.withObjectLimit(opts?.objectLimit);
        }
        return this.do(builder);
    }
    async nearObject(id, opts) {
        if (await this.grpcChecker) {
            return this.grpc()
                .then((aggregate) => aggregate.withNearObject(index_js_4.Serialize.aggregate.nearObject(id, opts)))
                .then((reply) => index_js_3.Deserialize.aggregate(reply));
        }
        const builder = this.base(opts?.returnMetrics, opts?.filters).withNearObject({
            id: id,
            certainty: opts?.certainty,
            distance: opts?.distance,
            targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
        });
        if (opts?.objectLimit) {
            builder.withObjectLimit(opts.objectLimit);
        }
        return this.do(builder);
    }
    async nearText(query, opts) {
        if (await this.grpcChecker) {
            return this.grpc()
                .then((aggregate) => aggregate.withNearText(index_js_4.Serialize.aggregate.nearText(query, opts)))
                .then((reply) => index_js_3.Deserialize.aggregate(reply));
        }
        const builder = this.base(opts?.returnMetrics, opts?.filters).withNearText({
            concepts: Array.isArray(query) ? query : [query],
            certainty: opts?.certainty,
            distance: opts?.distance,
            targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
        });
        if (opts?.objectLimit) {
            builder.withObjectLimit(opts.objectLimit);
        }
        return this.do(builder);
    }
    async nearVector(vector, opts) {
        if (await this.grpcChecker) {
            return this.grpc()
                .then(async (aggregate) => aggregate.withNearVector(await index_js_4.Serialize.aggregate.nearVector(vector, opts)))
                .then((reply) => index_js_3.Deserialize.aggregate(reply));
        }
        if (!utils_js_1.NearVectorInputGuards.is1D(vector)) {
            throw new errors_js_1.WeaviateInvalidInputError('Vector can only be a 1D array of numbers when using `nearVector` with <1.29 Weaviate versions.');
        }
        const builder = this.base(opts?.returnMetrics, opts?.filters).withNearVector({
            vector: vector,
            certainty: opts?.certainty,
            distance: opts?.distance,
            targetVectors: opts?.targetVector ? [opts.targetVector] : undefined,
        });
        if (opts?.objectLimit) {
            builder.withObjectLimit(opts.objectLimit);
        }
        return this.do(builder);
    }
    async overAll(opts) {
        if (await this.grpcChecker) {
            return this.grpc()
                .then((aggregate) => aggregate.withFetch(index_js_4.Serialize.aggregate.overAll(opts)))
                .then((reply) => index_js_3.Deserialize.aggregate(reply));
        }
        return this.do(this.base(opts?.returnMetrics, opts?.filters));
    }
    do = (query) => {
        return query
            .do()
            .then(({ data }) => {
            const { meta, ...rest } = data.Aggregate[this.name][0];
            return {
                properties: rest,
                totalCount: meta?.count,
            };
        })
            .catch((err) => {
            throw new errors_js_1.WeaviateQueryError(err.message, 'GraphQL');
        });
    };
    doGroupBy = (query) => {
        return query
            .do()
            .then(({ data }) => data.Aggregate[this.name].map((item) => {
            const { groupedBy, meta, ...rest } = item;
            return {
                groupedBy: {
                    prop: groupedBy.path[0],
                    value: groupedBy.value,
                },
                properties: rest,
                totalCount: meta?.count,
            };
        }))
            .catch((err) => {
            throw new errors_js_1.WeaviateQueryError(err.message, 'GraphQL');
        });
    };
}
exports.default = AggregateManager.use;
