"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constant_1 = require("./constant");
const index_1 = require("./index");
const validate_1 = require("./validate");
const util_1 = require("./util");
const query_1 = require("./serializer/query");
const update_1 = require("./serializer/update");
const websocket_client_1 = require("./realtime/websocket-client");
const constant_2 = require("./constant");
const utils_1 = require("./utils/utils");
const code_1 = require("./const/code");
const bson_1 = require("bson");
class Query {
    constructor(db, coll, fieldFilters, apiOptions, transactionId) {
        this.watch = (options) => {
            if (!index_1.Db.ws) {
                index_1.Db.ws = new websocket_client_1.RealtimeWebSocketClient({
                    context: {
                        appConfig: {
                            docSizeLimit: 1000,
                            realtimePingInterval: 10000,
                            realtimePongWaitTimeout: 5000,
                            request: this._request
                        }
                    }
                });
            }
            const { limit, order } = this._apiOptions;
            return index_1.Db.ws.watch(Object.assign(Object.assign({}, options), { envId: this._db.config.env, collectionName: this._coll, query: JSON.stringify(this._fieldFilters), limit, orderBy: order
                    ? order.reduce((acc, cur) => {
                        acc[cur.field] = cur.direction;
                        return acc;
                    }, {})
                    : undefined }));
        };
        this._db = db;
        this._coll = coll;
        this._fieldFilters = fieldFilters;
        this._apiOptions = apiOptions || {};
        this._request = new index_1.Db.reqClass(this._db.config);
        this._transactionId = transactionId;
    }
    async get() {
        const order = this._apiOptions.order;
        let param = {
            collectionName: this._coll,
            queryType: constant_1.QueryType.WHERE,
            transactionId: this._transactionId
        };
        if (this._fieldFilters) {
            param.query = this._fieldFilters;
        }
        if (order) {
            param.order = utils_1.stringifyByEJSON(order);
        }
        const offset = this._apiOptions.offset;
        if (offset) {
            param.offset = offset;
        }
        const limit = this._apiOptions.limit;
        if (limit) {
            param.limit = limit < 1000 ? limit : 1000;
        }
        else {
            param.limit = 100;
        }
        const projection = this._apiOptions.projection;
        if (projection) {
            param.projection = utils_1.stringifyByEJSON(projection);
        }
        const res = await this._request.send('database.getDocument', param, utils_1.getReqOpts(this._apiOptions));
        if (res.code) {
            return res;
        }
        const list = res.data.list.map(item => bson_1.EJSON.parse(item));
        const documents = util_1.Util.formatResDocumentData(list);
        const result = {
            data: documents,
            requestId: res.requestId
        };
        if (res.limit)
            result.limit = res.limit;
        if (res.offset)
            result.offset = res.offset;
        return result;
    }
    async count() {
        let param = {
            collectionName: this._coll,
            queryType: constant_1.QueryType.WHERE
        };
        if (this._fieldFilters) {
            param.query = this._fieldFilters;
        }
        const res = await this._request.send('database.calculateDocument', param, utils_1.getReqOpts(this._apiOptions));
        if (res.code) {
            return res;
        }
        return {
            requestId: res.requestId,
            total: res.data.total
        };
    }
    where(query) {
        if (Object.prototype.toString.call(query).slice(8, -1) !== 'Object') {
            throw Error(constant_2.ErrorCode.QueryParamTypeError);
        }
        const keys = Object.keys(query);
        const checkFlag = keys.some(item => {
            return query[item] !== undefined;
        });
        if (keys.length && !checkFlag) {
            throw Error(constant_2.ErrorCode.QueryParamValueError);
        }
        return new Query(this._db, this._coll, query_1.QuerySerializer.encodeEJSON(query), this._apiOptions, this._transactionId);
    }
    options(apiOptions) {
        validate_1.Validate.isValidOptions(apiOptions);
        return new Query(this._db, this._coll, this._fieldFilters, apiOptions, this._transactionId);
    }
    orderBy(fieldPath, directionStr) {
        validate_1.Validate.isFieldPath(fieldPath);
        validate_1.Validate.isFieldOrder(directionStr);
        const newOrder = {
            [fieldPath]: directionStr === 'desc' ? -1 : 1
        };
        const order = this._apiOptions.order || {};
        const newApiOption = Object.assign({}, this._apiOptions, {
            order: Object.assign({}, order, newOrder)
        });
        return new Query(this._db, this._coll, this._fieldFilters, newApiOption, this._transactionId);
    }
    limit(limit) {
        validate_1.Validate.isInteger('limit', limit);
        let newApiOption = Object.assign({}, this._apiOptions);
        newApiOption.limit = limit;
        return new Query(this._db, this._coll, this._fieldFilters, newApiOption, this._transactionId);
    }
    skip(offset) {
        validate_1.Validate.isInteger('offset', offset);
        let newApiOption = Object.assign({}, this._apiOptions);
        newApiOption.offset = offset;
        return new Query(this._db, this._coll, this._fieldFilters, newApiOption, this._transactionId);
    }
    async update(data) {
        if (!data || typeof data !== 'object') {
            return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '参数必需是非空对象' }));
        }
        if (data.hasOwnProperty('_id')) {
            return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '不能更新_id的值' }));
        }
        let { multiple } = this._apiOptions;
        const multi = multiple === undefined ? true : multiple;
        let param = {
            collectionName: this._coll,
            queryType: constant_1.QueryType.WHERE,
            multi,
            merge: true,
            upsert: false,
            data: update_1.UpdateSerializer.encodeEJSON(data)
        };
        if (this._fieldFilters) {
            param.query = this._fieldFilters;
        }
        const res = await this._request.send('database.modifyDocument', param, utils_1.getReqOpts(this._apiOptions));
        if (res.code) {
            return res;
        }
        return {
            requestId: res.requestId,
            updated: res.data.updated,
            upsertId: res.data.upsert_id
        };
    }
    field(projection) {
        let transformProjection = {};
        for (let k in projection) {
            if (typeof projection[k] === 'boolean') {
                transformProjection[k] = projection[k] === true ? 1 : 0;
            }
            if (typeof projection[k] === 'number') {
                transformProjection[k] = projection[k] > 0 ? 1 : 0;
            }
            if (typeof projection[k] === 'object') {
                transformProjection[k] = projection[k];
            }
        }
        let newApiOption = Object.assign({}, this._apiOptions);
        newApiOption.projection = transformProjection;
        return new Query(this._db, this._coll, this._fieldFilters, newApiOption, this._transactionId);
    }
    async remove() {
        const { offset, limit, projection, order } = this._apiOptions;
        if (offset !== undefined ||
            limit !== undefined ||
            projection !== undefined ||
            order !== undefined) {
            console.warn('`offset`, `limit`, `projection`, `orderBy` are not supported in remove() operation');
        }
        let { multiple } = this._apiOptions;
        const multi = multiple === undefined ? true : multiple;
        const param = {
            collectionName: this._coll,
            query: this._fieldFilters,
            queryType: constant_1.QueryType.WHERE,
            multi
        };
        const res = await this._request.send('database.removeDocument', param, utils_1.getReqOpts(this._apiOptions));
        if (res.code) {
            return res;
        }
        return {
            requestId: res.requestId,
            deleted: res.data.deleted
        };
    }
    async updateAndReturn(data) {
        if (!data || typeof data !== 'object') {
            return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '参数必需是非空对象' }));
        }
        if (data.hasOwnProperty('_id')) {
            return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '不能更新_id的值' }));
        }
        let param = {
            collectionName: this._coll,
            queryType: constant_1.QueryType.WHERE,
            data: update_1.UpdateSerializer.encodeEJSON(data)
        };
        if (this._transactionId) {
            param.transactionId = this._transactionId;
        }
        if (this._fieldFilters) {
            param.query = this._fieldFilters;
        }
        const res = await this._request.send('database.modifyAndReturnDoc', param, utils_1.getReqOpts(this._apiOptions));
        if (res.code) {
            return res;
        }
        return {
            requestId: res.requestId,
            updated: res.data.updated,
            doc: res.data.doc && bson_1.EJSON.parse(res.data.doc)
        };
    }
}
exports.Query = Query;
