"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestItemRootImpl = exports.TestItemImpl = exports.MixedTestItemController = exports.InvalidTestItemError = exports.DuplicateTestItemError = exports.diffTestItems = exports.getPrivateApiFor = exports.createPrivateApiFor = void 0;
const eventPrivateApis = new WeakMap();
const createPrivateApiFor = (impl, controllerId) => {
    const api = { controllerId };
    eventPrivateApis.set(impl, api);
    return api;
};
exports.createPrivateApiFor = createPrivateApiFor;
/**
 * Gets the private API for a test item implementation. This implementation
 * is a managed object, but we keep a weakmap to avoid exposing any of the
 * internals to extensions.
 */
const getPrivateApiFor = (impl) => eventPrivateApis.get(impl);
exports.getPrivateApiFor = getPrivateApiFor;
const testItemPropAccessor = (api, key, defaultValue, equals) => {
    let value = defaultValue;
    return {
        enumerable: true,
        configurable: false,
        get() {
            return value;
        },
        set(newValue) {
            var _a;
            if (!equals(value, newValue)) {
                const oldValue = value;
                value = newValue;
                (_a = api.listener) === null || _a === void 0 ? void 0 : _a.call(api, {
                    op: 3 /* ExtHostTestItemEventOp.SetProp */,
                    key,
                    value: newValue,
                    previous: oldValue,
                });
            }
        },
    };
};
const strictEqualComparator = (a, b) => a === b;
const propComparators = {
    range: (a, b) => {
        if (a === b) {
            return true;
        }
        if (!a || !b) {
            return false;
        }
        return a.isEqual(b);
    },
    label: strictEqualComparator,
    description: strictEqualComparator,
    sortText: strictEqualComparator,
    busy: strictEqualComparator,
    error: strictEqualComparator,
    canResolveChildren: strictEqualComparator,
    tags: (a, b) => {
        if (a.length !== b.length) {
            return false;
        }
        if (a.some((t1) => !b.find((t2) => t1.id === t2.id))) {
            return false;
        }
        return true;
    },
};
const writablePropKeys = Object.keys(propComparators);
const makePropDescriptors = (api, label) => ({
    range: testItemPropAccessor(api, 'range', undefined, propComparators.range),
    label: testItemPropAccessor(api, 'label', label, propComparators.label),
    description: testItemPropAccessor(api, 'description', undefined, propComparators.description),
    sortText: testItemPropAccessor(api, 'sortText', undefined, propComparators.sortText),
    canResolveChildren: testItemPropAccessor(api, 'canResolveChildren', false, propComparators.canResolveChildren),
    busy: testItemPropAccessor(api, 'busy', false, propComparators.busy),
    error: testItemPropAccessor(api, 'error', undefined, propComparators.error),
    tags: testItemPropAccessor(api, 'tags', [], propComparators.tags),
});
/**
 * Returns a partial test item containing the writable properties in B that
 * are different from A.
 */
const diffTestItems = (a, b) => {
    const output = new Map();
    for (const key of writablePropKeys) {
        const cmp = propComparators[key];
        if (!cmp(a[key], b[key])) {
            output.set(key, b[key]);
        }
    }
    return output;
};
exports.diffTestItems = diffTestItems;
class DuplicateTestItemError extends Error {
    constructor(id) {
        super(`Attempted to insert a duplicate test item ID ${id}`);
    }
}
exports.DuplicateTestItemError = DuplicateTestItemError;
class InvalidTestItemError extends Error {
    constructor(id) {
        super(`TestItem with ID "${id}" is invalid. Make sure to create it from the createTestItem method.`);
    }
}
exports.InvalidTestItemError = InvalidTestItemError;
class MixedTestItemController extends Error {
    constructor(id, ctrlA, ctrlB) {
        super(`TestItem with ID "${id}" is from controller "${ctrlA}" and cannot be added as a child of an item from controller "${ctrlB}".`);
    }
}
exports.MixedTestItemController = MixedTestItemController;
const createTestItemCollection = (owningItem) => {
    const api = (0, exports.getPrivateApiFor)(owningItem);
    let mapped = new Map();
    return {
        /** @inheritdoc */
        get size() {
            return mapped.size;
        },
        /** @inheritdoc */
        forEach(callback, thisArg) {
            for (const item of mapped.values()) {
                callback.call(thisArg, item, this);
            }
        },
        /** @inheritdoc */
        replace(items) {
            var _a;
            const newMapped = new Map();
            const toDelete = new Set(mapped.keys());
            const bulk = { op: 4 /* ExtHostTestItemEventOp.Bulk */, ops: [] };
            for (const item of items) {
                if (!(item instanceof TestItemImpl)) {
                    throw new InvalidTestItemError(item.id);
                }
                const itemController = (0, exports.getPrivateApiFor)(item).controllerId;
                if (itemController !== api.controllerId) {
                    throw new MixedTestItemController(item.id, itemController, api.controllerId);
                }
                if (newMapped.has(item.id)) {
                    throw new DuplicateTestItemError(item.id);
                }
                newMapped.set(item.id, item);
                toDelete.delete(item.id);
                bulk.ops.push({ op: 0 /* ExtHostTestItemEventOp.Upsert */, item });
            }
            for (const id of toDelete.keys()) {
                bulk.ops.push({ op: 1 /* ExtHostTestItemEventOp.RemoveChild */, id });
            }
            (_a = api.listener) === null || _a === void 0 ? void 0 : _a.call(api, bulk);
            // important mutations come after firing, so if an error happens no
            // changes will be "saved":
            mapped = newMapped;
        },
        add(item) {
            var _a;
            if (!(item instanceof TestItemImpl)) {
                throw new InvalidTestItemError(item.id);
            }
            mapped.set(item.id, item);
            (_a = api.listener) === null || _a === void 0 ? void 0 : _a.call(api, { op: 0 /* ExtHostTestItemEventOp.Upsert */, item });
        },
        /** @inheritdoc */
        delete(id) {
            var _a;
            if (mapped.delete(id)) {
                (_a = api.listener) === null || _a === void 0 ? void 0 : _a.call(api, { op: 1 /* ExtHostTestItemEventOp.RemoveChild */, id });
            }
        },
        /** @inheritdoc */
        get(itemId) {
            return mapped.get(itemId);
        },
        /** JSON serialization function. */
        toJSON() {
            return Array.from(mapped.values());
        },
        /** @inheritdoc */
        [Symbol.iterator]() {
            return mapped.values();
        },
    };
};
class TestItemImpl {
    /**
     * Note that data is deprecated and here for back-compat only
     */
    constructor(controllerId, id, label, uri) {
        if (id.includes("\0" /* TestIdPathParts.Delimiter */)) {
            throw new Error(`Test IDs may not include the ${JSON.stringify(id)} symbol`);
        }
        const api = (0, exports.createPrivateApiFor)(this, controllerId);
        Object.defineProperties(this, {
            id: {
                value: id,
                enumerable: true,
                writable: false,
            },
            uri: {
                value: uri,
                enumerable: true,
                writable: false,
            },
            parent: {
                enumerable: false,
                get() {
                    return api.parent instanceof TestItemRootImpl ? undefined : api.parent;
                },
            },
            children: {
                value: createTestItemCollection(this),
                enumerable: true,
                writable: false,
            },
            ...makePropDescriptors(api, label),
        });
    }
    /** @deprecated back compat */
    invalidateResults() {
        var _a, _b;
        (_b = (_a = (0, exports.getPrivateApiFor)(this)).listener) === null || _b === void 0 ? void 0 : _b.call(_a, { op: 2 /* ExtHostTestItemEventOp.Invalidated */ });
    }
}
exports.TestItemImpl = TestItemImpl;
class TestItemRootImpl extends TestItemImpl {
    constructor(controllerId, label) {
        super(controllerId, controllerId, label, undefined);
    }
}
exports.TestItemRootImpl = TestItemRootImpl;
//# sourceMappingURL=testApi.js.map