import { EJSON } from 'bson';
import { Db } from './index';
class DocumentSnapshot {
    constructor(data, requestId) {
        this._data = data;
        this.requestId = requestId;
    }
    data() {
        return this._data;
    }
}
const START = 'database.startTransaction';
const COMMIT = 'database.commitTransaction';
const ABORT = 'database.abortTransaction';
const GET_DOC = 'database.getInTransaction';
const UPDATE_DOC = 'database.updateDocInTransaction';
const DELETE_DOC = 'database.deleteDocInTransaction';
export class Transaction {
    constructor(db) {
        this._db = db;
        this._request = new Db.reqClass(this._db.config);
    }
    async init() {
        const res = await this._request.send(START, {});
        if (res.code) {
            throw res;
        }
        this._id = res.transactionId;
    }
    async get(documentRef) {
        const param = {
            collectionName: documentRef._coll,
            transactionId: this._id,
            _id: documentRef.id
        };
        const res = await this._request.send(GET_DOC, param);
        if (res.code)
            throw res;
        return new DocumentSnapshot(EJSON.parse(res.data), res.requestId);
    }
    async set(documentRef, data) {
        const param = {
            collectionName: documentRef._coll,
            transactionId: this._id,
            _id: documentRef.id,
            data: EJSON.stringify(data, { relaxed: false }),
            upsert: true
        };
        const res = await this._request.send(UPDATE_DOC, param);
        if (res.code)
            throw res;
        return Object.assign(Object.assign({}, res), { updated: EJSON.parse(res.updated), upserted: res.upserted
                ? JSON.parse(res.upserted)
                : null });
    }
    async update(documentRef, data) {
        const param = {
            collectionName: documentRef._coll,
            transactionId: this._id,
            _id: documentRef.id,
            data: EJSON.stringify({
                $set: data
            }, {
                relaxed: false
            })
        };
        const res = await this._request.send(UPDATE_DOC, param);
        if (res.code)
            throw res;
        return Object.assign(Object.assign({}, res), { updated: EJSON.parse(res.updated) });
    }
    async delete(documentRef) {
        const param = {
            collectionName: documentRef._coll,
            transactionId: this._id,
            _id: documentRef.id
        };
        const res = await this._request.send(DELETE_DOC, param);
        if (res.code)
            throw res;
        return Object.assign(Object.assign({}, res), { deleted: EJSON.parse(res.deleted) });
    }
    async commit() {
        const param = {
            transactionId: this._id
        };
        const res = await this._request.send(COMMIT, param);
        if (res.code)
            throw res;
        return res;
    }
    async rollback() {
        const param = {
            transactionId: this._id
        };
        const res = await this._request.send(ABORT, param);
        if (res.code)
            throw res;
        return res;
    }
}
export async function startTransaction() {
    const transaction = new Transaction(this);
    await transaction.init();
    return transaction;
}
export async function runTransaction(callback, times = 3) {
    if (times <= 0) {
        throw new Error('Transaction failed');
    }
    try {
        const transaction = new Transaction(this);
        await transaction.init();
        await callback(transaction);
        await transaction.commit();
    }
    catch (error) {
        console.log(error);
        return runTransaction.bind(this)(callback, --times);
    }
}
