"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SendListReadable = void 0;
const stream_1 = require("stream");
const lodash_1 = __importDefault(require("lodash"));
const cross_1 = require("./cross");
const events_1 = __importDefault(require("./events"));
// offer an api for a generic dynamic list. Suitable to be the result of an api.
class SendListReadable extends stream_1.Readable {
    lastError;
    buffer = [];
    processBuffer;
    sent;
    constructor({ addAtStart, doAtStart, bufferTime, onEnd, diff } = {}) {
        super({ objectMode: true, read() { } });
        if (!bufferTime)
            bufferTime = 200;
        if (diff)
            this.sent = [];
        this.processBuffer = lodash_1.default.debounce(() => {
            const { sent } = this;
            if (sent)
                this.buffer = this.buffer.filter(([cmd, a, b]) => {
                    if (cmd === cross_1.LIST.add)
                        return sent.push(...(0, cross_1.wantArray)(a));
                    if (cmd === cross_1.LIST.remove)
                        return lodash_1.default.remove(sent, a);
                    if (cmd !== cross_1.LIST.update)
                        return true;
                    const found = lodash_1.default.find(sent, a);
                    if (!found)
                        return;
                    for (const k in b)
                        if (b[k] === found[k])
                            delete b[k];
                        else {
                            found[k] = b[k];
                            b[k] ??= null; // go and delete it, remotely
                        }
                    return !lodash_1.default.isEmpty(b);
                });
            if (!this.buffer.length)
                return;
            this.push(this.buffer);
            this.buffer = [];
        }, bufferTime, { maxWait: bufferTime });
        this.on('close', () => {
            onEnd?.(this);
            this.destroy();
        });
        setTimeout(() => doAtStart?.(this)); // work later, when list object has been received by Koa
        if (addAtStart) {
            for (const x of addAtStart)
                this.add(x);
            this.ready();
        }
    }
    _push(rec) {
        this.buffer.push(rec);
        if (this.buffer.length > 10_000) // hard limit
            this.processBuffer.flush();
        else
            this.processBuffer();
    }
    add(rec) {
        this._push([cross_1.LIST.add, rec]);
    }
    remove(search) {
        const match = lodash_1.default.matches(search);
        const idx = lodash_1.default.findIndex(this.buffer, x => match(x[1]));
        const found = this.buffer[idx];
        const op = found?.[0];
        if (op === cross_1.LIST.remove)
            return;
        if (found) {
            this.buffer.splice(idx, 1);
            if (op === cross_1.LIST.add)
                return; // assuming this never reached the client
        }
        this._push([cross_1.LIST.remove, search]);
    }
    update(search, change) {
        if (lodash_1.default.isEmpty(change))
            return;
        const match = lodash_1.default.matches(search);
        const found = lodash_1.default.find(this.buffer, x => match(x[1]));
        const op = found?.[0];
        if (op === cross_1.LIST.remove)
            return;
        if (op === cross_1.LIST.add || op === cross_1.LIST.update)
            return Object.assign(found[op === cross_1.LIST.add ? 1 : 2], change);
        this._push([cross_1.LIST.update, search, change]);
    }
    ready() {
        this._push([cross_1.LIST.ready]);
    }
    custom(name, data) {
        this._push(data === undefined ? [name] : [name, data]);
    }
    props(props) {
        this._push([cross_1.LIST.props, props]);
    }
    error(msg, close = false, props) {
        this._push([cross_1.LIST.error, msg, props]);
        this.lastError = msg;
        if (close)
            this.close();
    }
    getLastError() {
        return this.lastError;
    }
    close() {
        this.processBuffer.flush();
        this.push(null);
    }
    events(ctx, eventMap, options) {
        ctx.res.once('close', events_1.default.multi(eventMap, options));
        return this;
    }
    isClosed() {
        return this.destroyed;
    }
}
exports.SendListReadable = SendListReadable;
