"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.deleteObject = exports.addOrUpdateObject = exports.deleteItems = exports.cacheMapFromList = exports.ListWatch = void 0;
const api_1 = require("./api");
const informer_1 = require("./informer");
class ListWatch {
    constructor(path, watch, listFn, autoStart = true, labelSelector, fieldSelector) {
        this.path = path;
        this.watch = watch;
        this.listFn = listFn;
        this.labelSelector = labelSelector;
        this.fieldSelector = fieldSelector;
        this.objects = new Map();
        this.callbackCache = {};
        this.stopped = false;
        this.callbackCache[informer_1.ADD] = [];
        this.callbackCache[informer_1.UPDATE] = [];
        this.callbackCache[informer_1.DELETE] = [];
        this.callbackCache[informer_1.ERROR] = [];
        this.callbackCache[informer_1.CONNECT] = [];
        this.resourceVersion = '';
        if (autoStart) {
            this.doneHandler(null);
        }
    }
    async start() {
        this.stopped = false;
        await this.doneHandler(null);
    }
    async stop() {
        this.stopped = true;
        this._stop();
    }
    on(verb, cb) {
        if (verb === informer_1.CHANGE) {
            this.on(informer_1.ADD, cb);
            this.on(informer_1.UPDATE, cb);
            this.on(informer_1.DELETE, cb);
            return;
        }
        if (this.callbackCache[verb] === undefined) {
            throw new Error(`Unknown verb: ${verb}`);
        }
        this.callbackCache[verb].push(cb);
    }
    off(verb, cb) {
        if (verb === informer_1.CHANGE) {
            this.off(informer_1.ADD, cb);
            this.off(informer_1.UPDATE, cb);
            this.off(informer_1.DELETE, cb);
            return;
        }
        if (this.callbackCache[verb] === undefined) {
            throw new Error(`Unknown verb: ${verb}`);
        }
        const indexToRemove = this.callbackCache[verb].findIndex((cachedCb) => cachedCb === cb);
        if (indexToRemove === -1) {
            return;
        }
        this.callbackCache[verb].splice(indexToRemove, 1);
    }
    get(name, namespace) {
        const nsObjects = this.objects.get(namespace || '');
        if (nsObjects) {
            return nsObjects.get(name);
        }
        return undefined;
    }
    list(namespace) {
        if (!namespace) {
            const allObjects = [];
            for (const nsObjects of this.objects.values()) {
                allObjects.push(...nsObjects.values());
            }
            return allObjects;
        }
        const namespaceObjects = this.objects.get(namespace || '');
        if (!namespaceObjects) {
            return [];
        }
        return Array.from(namespaceObjects.values());
    }
    latestResourceVersion() {
        return this.resourceVersion;
    }
    _stop() {
        if (this.request) {
            this.request.removeAllListeners('error');
            this.request.on('error', () => {
                // void - errors emitted post-abort are not relevant for us
            });
            this.request.abort();
            this.request = undefined;
        }
    }
    async doneHandler(err) {
        this._stop();
        if (err &&
            (err.statusCode === 410 || err.code === 410)) {
            this.resourceVersion = '';
        }
        else if (err) {
            this.callbackCache[informer_1.ERROR].forEach((elt) => elt(err));
            return;
        }
        if (this.stopped) {
            // do not auto-restart
            return;
        }
        this.callbackCache[informer_1.CONNECT].forEach((elt) => elt(undefined));
        if (!this.resourceVersion) {
            const promise = this.listFn();
            const result = await promise;
            const list = result.body;
            this.objects = deleteItems(this.objects, list.items, this.callbackCache[informer_1.DELETE].slice());
            this.addOrUpdateItems(list.items);
            this.resourceVersion = list.metadata.resourceVersion || '';
        }
        const queryParams = {
            resourceVersion: this.resourceVersion,
        };
        if (this.labelSelector !== undefined) {
            queryParams.labelSelector = api_1.ObjectSerializer.serialize(this.labelSelector, 'string');
        }
        if (this.fieldSelector !== undefined) {
            queryParams.fieldSelector = api_1.ObjectSerializer.serialize(this.fieldSelector, 'string');
        }
        this.request = await this.watch.watch(this.path, queryParams, this.watchHandler.bind(this), this.doneHandler.bind(this));
    }
    addOrUpdateItems(items) {
        items.forEach((obj) => {
            addOrUpdateObject(this.objects, obj, this.callbackCache[informer_1.ADD].slice(), this.callbackCache[informer_1.UPDATE].slice());
        });
    }
    async watchHandler(phase, obj, watchObj) {
        switch (phase) {
            case 'ADDED':
            case 'MODIFIED':
                addOrUpdateObject(this.objects, obj, this.callbackCache[informer_1.ADD].slice(), this.callbackCache[informer_1.UPDATE].slice());
                break;
            case 'DELETED':
                deleteObject(this.objects, obj, this.callbackCache[informer_1.DELETE].slice());
                break;
            case 'BOOKMARK':
                // nothing to do, here for documentation, mostly.
                break;
            case 'ERROR':
                await this.doneHandler(obj);
                return;
        }
        this.resourceVersion = obj.metadata.resourceVersion || '';
    }
}
exports.ListWatch = ListWatch;
// exported for testing
function cacheMapFromList(newObjects) {
    const objects = new Map();
    // build up the new list
    for (const obj of newObjects) {
        let namespaceObjects = objects.get(obj.metadata.namespace || '');
        if (!namespaceObjects) {
            namespaceObjects = new Map();
            objects.set(obj.metadata.namespace || '', namespaceObjects);
        }
        const name = obj.metadata.name || '';
        namespaceObjects.set(name, obj);
    }
    return objects;
}
exports.cacheMapFromList = cacheMapFromList;
// external for testing
function deleteItems(oldObjects, newObjects, deleteCallback) {
    const newObjectsMap = cacheMapFromList(newObjects);
    for (const [namespace, oldNamespaceObjects] of oldObjects.entries()) {
        const newNamespaceObjects = newObjectsMap.get(namespace);
        if (newNamespaceObjects) {
            for (const [name, oldObj] of oldNamespaceObjects.entries()) {
                if (!newNamespaceObjects.has(name)) {
                    oldNamespaceObjects.delete(name);
                    if (deleteCallback) {
                        deleteCallback.forEach((fn) => fn(oldObj));
                    }
                }
            }
        }
        else {
            oldObjects.delete(namespace);
            oldNamespaceObjects.forEach((obj) => {
                if (deleteCallback) {
                    deleteCallback.forEach((fn) => fn(obj));
                }
            });
        }
    }
    return oldObjects;
}
exports.deleteItems = deleteItems;
// Only public for testing.
function addOrUpdateObject(objects, obj, addCallbacks, updateCallbacks) {
    let namespaceObjects = objects.get(obj.metadata.namespace || '');
    if (!namespaceObjects) {
        namespaceObjects = new Map();
        objects.set(obj.metadata.namespace || '', namespaceObjects);
    }
    const name = obj.metadata.name || '';
    const found = namespaceObjects.get(name);
    if (!found) {
        namespaceObjects.set(name, obj);
        if (addCallbacks) {
            addCallbacks.forEach((elt) => elt(obj));
        }
    }
    else {
        if (!isSameVersion(found, obj)) {
            namespaceObjects.set(name, obj);
            if (updateCallbacks) {
                updateCallbacks.forEach((elt) => elt(obj));
            }
        }
    }
}
exports.addOrUpdateObject = addOrUpdateObject;
function isSameVersion(o1, o2) {
    return (o1.metadata.resourceVersion !== undefined &&
        o1.metadata.resourceVersion !== null &&
        o1.metadata.resourceVersion === o2.metadata.resourceVersion);
}
// Public for testing.
function deleteObject(objects, obj, deleteCallbacks) {
    const namespace = obj.metadata.namespace || '';
    const name = obj.metadata.name || '';
    const namespaceObjects = objects.get(namespace);
    if (!namespaceObjects) {
        return;
    }
    const deleted = namespaceObjects.delete(name);
    if (deleted) {
        if (deleteCallbacks) {
            deleteCallbacks.forEach((elt) => elt(obj));
        }
        if (namespaceObjects.size === 0) {
            objects.delete(namespace);
        }
    }
}
exports.deleteObject = deleteObject;
//# sourceMappingURL=cache.js.map