"use strict";
/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */
Object.defineProperty(exports, "__esModule", { value: true });
exports.NotebookDocumentSyncFeature = void 0;
const vscode = require("vscode");
const minimatch = require("minimatch");
const proto = require("vscode-languageserver-protocol");
const UUID = require("./utils/uuid");
const Is = require("./utils/is");
function ensure(target, key) {
    if (target[key] === void 0) {
        target[key] = {};
    }
    return target[key];
}
var Converter;
(function (Converter) {
    let c2p;
    (function (c2p) {
        function asVersionedNotebookDocumentIdentifier(notebookDocument, base) {
            return {
                version: notebookDocument.version,
                uri: base.asUri(notebookDocument.uri)
            };
        }
        c2p.asVersionedNotebookDocumentIdentifier = asVersionedNotebookDocumentIdentifier;
        function asNotebookDocument(notebookDocument, cells, base) {
            const result = proto.NotebookDocument.create(base.asUri(notebookDocument.uri), notebookDocument.notebookType, notebookDocument.version, asNotebookCells(cells, base));
            if (Object.keys(notebookDocument.metadata).length > 0) {
                result.metadata = asMetadata(notebookDocument.metadata);
            }
            return result;
        }
        c2p.asNotebookDocument = asNotebookDocument;
        function asNotebookCells(cells, base) {
            return cells.map(cell => asNotebookCell(cell, base));
        }
        c2p.asNotebookCells = asNotebookCells;
        function asMetadata(metadata) {
            const seen = new Set();
            return deepCopy(seen, metadata);
        }
        c2p.asMetadata = asMetadata;
        function asNotebookCell(cell, base) {
            const result = proto.NotebookCell.create(asNotebookCellKind(cell.kind), base.asUri(cell.document.uri));
            if (Object.keys(cell.metadata).length > 0) {
                result.metadata = asMetadata(cell.metadata);
            }
            if (cell.executionSummary !== undefined && (Is.number(cell.executionSummary.executionOrder) && Is.boolean(cell.executionSummary.success))) {
                result.executionSummary = {
                    executionOrder: cell.executionSummary.executionOrder,
                    success: cell.executionSummary.success
                };
            }
            return result;
        }
        c2p.asNotebookCell = asNotebookCell;
        function asNotebookCellKind(kind) {
            switch (kind) {
                case vscode.NotebookCellKind.Markup:
                    return proto.NotebookCellKind.Markup;
                case vscode.NotebookCellKind.Code:
                    return proto.NotebookCellKind.Code;
            }
        }
        function deepCopy(seen, value) {
            if (seen.has(value)) {
                throw new Error(`Can't deep copy cyclic structures.`);
            }
            if (Array.isArray(value)) {
                const result = [];
                for (const elem of value) {
                    if (elem !== null && typeof elem === 'object' || Array.isArray(elem)) {
                        result.push(deepCopy(seen, elem));
                    }
                    else {
                        if (elem instanceof RegExp) {
                            throw new Error(`Can't transfer regular expressions to the server`);
                        }
                        result.push(elem);
                    }
                }
                return result;
            }
            else {
                const props = Object.keys(value);
                const result = Object.create(null);
                for (const prop of props) {
                    const elem = value[prop];
                    if (elem !== null && typeof elem === 'object' || Array.isArray(elem)) {
                        result[prop] = deepCopy(seen, elem);
                    }
                    else {
                        if (elem instanceof RegExp) {
                            throw new Error(`Can't transfer regular expressions to the server`);
                        }
                        result[prop] = elem;
                    }
                }
                return result;
            }
        }
        function asTextContentChange(event, base) {
            const params = base.asChangeTextDocumentParams(event, event.document.uri, event.document.version);
            return { document: params.textDocument, changes: params.contentChanges };
        }
        c2p.asTextContentChange = asTextContentChange;
        function asNotebookDocumentChangeEvent(event, base) {
            const result = Object.create(null);
            if (event.metadata) {
                result.metadata = Converter.c2p.asMetadata(event.metadata);
            }
            if (event.cells !== undefined) {
                const cells = Object.create(null);
                const changedCells = event.cells;
                if (changedCells.structure) {
                    cells.structure = {
                        array: {
                            start: changedCells.structure.array.start,
                            deleteCount: changedCells.structure.array.deleteCount,
                            cells: changedCells.structure.array.cells !== undefined ? changedCells.structure.array.cells.map(cell => Converter.c2p.asNotebookCell(cell, base)) : undefined
                        },
                        didOpen: changedCells.structure.didOpen !== undefined
                            ? changedCells.structure.didOpen.map(cell => base.asOpenTextDocumentParams(cell.document).textDocument)
                            : undefined,
                        didClose: changedCells.structure.didClose !== undefined
                            ? changedCells.structure.didClose.map(cell => base.asCloseTextDocumentParams(cell.document).textDocument)
                            : undefined
                    };
                }
                if (changedCells.data !== undefined) {
                    cells.data = changedCells.data.map(cell => Converter.c2p.asNotebookCell(cell, base));
                }
                if (changedCells.textContent !== undefined) {
                    cells.textContent = changedCells.textContent.map(event => Converter.c2p.asTextContentChange(event, base));
                }
                if (Object.keys(cells).length > 0) {
                    result.cells = cells;
                }
            }
            return result;
        }
        c2p.asNotebookDocumentChangeEvent = asNotebookDocumentChangeEvent;
    })(c2p = Converter.c2p || (Converter.c2p = {}));
})(Converter || (Converter = {}));
var $NotebookCell;
(function ($NotebookCell) {
    function computeDiff(originalCells, modifiedCells, compareMetadata) {
        const originalLength = originalCells.length;
        const modifiedLength = modifiedCells.length;
        let startIndex = 0;
        while (startIndex < modifiedLength && startIndex < originalLength && equals(originalCells[startIndex], modifiedCells[startIndex], compareMetadata)) {
            startIndex++;
        }
        if (startIndex < modifiedLength && startIndex < originalLength) {
            let originalEndIndex = originalLength - 1;
            let modifiedEndIndex = modifiedLength - 1;
            while (originalEndIndex >= 0 && modifiedEndIndex >= 0 && equals(originalCells[originalEndIndex], modifiedCells[modifiedEndIndex], compareMetadata)) {
                originalEndIndex--;
                modifiedEndIndex--;
            }
            const deleteCount = (originalEndIndex + 1) - startIndex;
            const newCells = startIndex === modifiedEndIndex + 1 ? undefined : modifiedCells.slice(startIndex, modifiedEndIndex + 1);
            return newCells !== undefined ? { start: startIndex, deleteCount, cells: newCells } : { start: startIndex, deleteCount };
        }
        else if (startIndex < modifiedLength) {
            return { start: startIndex, deleteCount: 0, cells: modifiedCells.slice(startIndex) };
        }
        else if (startIndex < originalLength) {
            return { start: startIndex, deleteCount: originalLength - startIndex };
        }
        else {
            // The two arrays are the same.
            return undefined;
        }
    }
    $NotebookCell.computeDiff = computeDiff;
    /**
     * We only sync kind, document, execution and metadata to the server. So we only need to compare those.
     */
    function equals(one, other, compareMetaData = true) {
        if (one.kind !== other.kind || one.document.uri.toString() !== other.document.uri.toString() || one.document.languageId !== other.document.languageId ||
            !equalsExecution(one.executionSummary, other.executionSummary)) {
            return false;
        }
        return !compareMetaData || (compareMetaData && equalsMetadata(one.metadata, other.metadata));
    }
    function equalsExecution(one, other) {
        if (one === other) {
            return true;
        }
        if (one === undefined || other === undefined) {
            return false;
        }
        return one.executionOrder === other.executionOrder && one.success === other.success && equalsTiming(one.timing, other.timing);
    }
    function equalsTiming(one, other) {
        if (one === other) {
            return true;
        }
        if (one === undefined || other === undefined) {
            return false;
        }
        return one.startTime === other.startTime && one.endTime === other.endTime;
    }
    function equalsMetadata(one, other) {
        if (one === other) {
            return true;
        }
        if (one === null || one === undefined || other === null || other === undefined) {
            return false;
        }
        if (typeof one !== typeof other) {
            return false;
        }
        if (typeof one !== 'object') {
            return false;
        }
        const oneArray = Array.isArray(one);
        const otherArray = Array.isArray(other);
        if (oneArray !== otherArray) {
            return false;
        }
        if (oneArray && otherArray) {
            if (one.length !== other.length) {
                return false;
            }
            for (let i = 0; i < one.length; i++) {
                if (!equalsMetadata(one[i], other[i])) {
                    return false;
                }
            }
        }
        if (isObjectLiteral(one) && isObjectLiteral(other)) {
            const oneKeys = Object.keys(one);
            const otherKeys = Object.keys(other);
            if (oneKeys.length !== otherKeys.length) {
                return false;
            }
            oneKeys.sort();
            otherKeys.sort();
            if (!equalsMetadata(oneKeys, otherKeys)) {
                return false;
            }
            for (let i = 0; i < oneKeys.length; i++) {
                const prop = oneKeys[i];
                if (!equalsMetadata(one[prop], other[prop])) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
    function isObjectLiteral(value) {
        return value !== null && typeof value === 'object';
    }
    $NotebookCell.isObjectLiteral = isObjectLiteral;
})($NotebookCell || ($NotebookCell = {}));
var $NotebookDocumentFilter;
(function ($NotebookDocumentFilter) {
    function matchNotebook(filter, notebookDocument) {
        if (typeof filter === 'string') {
            return filter === '*' || notebookDocument.notebookType === filter;
        }
        if (filter.notebookType !== undefined && filter.notebookType !== '*' && notebookDocument.notebookType !== filter.notebookType) {
            return false;
        }
        const uri = notebookDocument.uri;
        if (filter.scheme !== undefined && filter.scheme !== '*' && uri.scheme !== filter.scheme) {
            return false;
        }
        if (filter.pattern !== undefined) {
            const matcher = new minimatch.Minimatch(filter.pattern, { noext: true });
            if (!matcher.makeRe()) {
                return false;
            }
            if (!matcher.match(uri.fsPath)) {
                return false;
            }
        }
        return true;
    }
    $NotebookDocumentFilter.matchNotebook = matchNotebook;
})($NotebookDocumentFilter || ($NotebookDocumentFilter = {}));
var $NotebookDocumentSyncOptions;
(function ($NotebookDocumentSyncOptions) {
    function asDocumentSelector(options) {
        const selector = options.notebookSelector;
        const result = [];
        for (const element of selector) {
            const notebookType = (typeof element.notebook === 'string' ? element.notebook : element.notebook?.notebookType) ?? '*';
            const scheme = (typeof element.notebook === 'string') ? undefined : element.notebook?.scheme;
            const pattern = (typeof element.notebook === 'string') ? undefined : element.notebook?.pattern;
            if (element.cells !== undefined) {
                for (const cell of element.cells) {
                    result.push(asDocumentFilter(notebookType, scheme, pattern, cell.language));
                }
            }
            else {
                result.push(asDocumentFilter(notebookType, scheme, pattern, undefined));
            }
        }
        return result;
    }
    $NotebookDocumentSyncOptions.asDocumentSelector = asDocumentSelector;
    function asDocumentFilter(notebookType, scheme, pattern, language) {
        return scheme === undefined && pattern === undefined
            ? { notebook: notebookType, language }
            : { notebook: { notebookType, scheme, pattern }, language };
    }
})($NotebookDocumentSyncOptions || ($NotebookDocumentSyncOptions = {}));
var SyncInfo;
(function (SyncInfo) {
    function create(cells) {
        return {
            cells,
            uris: new Set(cells.map(cell => cell.document.uri.toString()))
        };
    }
    SyncInfo.create = create;
})(SyncInfo || (SyncInfo = {}));
class NotebookDocumentSyncFeatureProvider {
    constructor(client, options) {
        this.client = client;
        this.options = options;
        this.notebookSyncInfo = new Map();
        this.notebookDidOpen = new Set();
        this.disposables = [];
        this.selector = client.protocol2CodeConverter.asDocumentSelector($NotebookDocumentSyncOptions.asDocumentSelector(options));
        // open
        vscode.workspace.onDidOpenNotebookDocument((notebookDocument) => {
            this.notebookDidOpen.add(notebookDocument.uri.toString());
            this.didOpen(notebookDocument);
        }, undefined, this.disposables);
        for (const notebookDocument of vscode.workspace.notebookDocuments) {
            this.notebookDidOpen.add(notebookDocument.uri.toString());
            this.didOpen(notebookDocument);
        }
        // Notebook document changed.
        vscode.workspace.onDidChangeNotebookDocument(event => this.didChangeNotebookDocument(event), undefined, this.disposables);
        //save
        if (this.options.save === true) {
            vscode.workspace.onDidSaveNotebookDocument(notebookDocument => this.didSave(notebookDocument), undefined, this.disposables);
        }
        // close
        vscode.workspace.onDidCloseNotebookDocument((notebookDocument) => {
            this.didClose(notebookDocument);
            this.notebookDidOpen.delete(notebookDocument.uri.toString());
        }, undefined, this.disposables);
    }
    getState() {
        for (const notebook of vscode.workspace.notebookDocuments) {
            const matchingCells = this.getMatchingCells(notebook);
            if (matchingCells !== undefined) {
                return { kind: 'document', id: '$internal', registrations: true, matches: true };
            }
        }
        return { kind: 'document', id: '$internal', registrations: true, matches: false };
    }
    get mode() {
        return 'notebook';
    }
    handles(textDocument) {
        return vscode.languages.match(this.selector, textDocument) > 0;
    }
    didOpenNotebookCellTextDocument(notebookDocument, cell) {
        if (vscode.languages.match(this.selector, cell.document) === 0) {
            return;
        }
        if (!this.notebookDidOpen.has(notebookDocument.uri.toString())) {
            // We have never received an open notification for the notebook document.
            // VS Code guarantees that we first get cell document open and then
            // notebook open. So simply wait for the notebook open.
            return;
        }
        const syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString());
        // In VS Code we receive a notebook open before a cell document open.
        // The document and the cell is synced.
        const cellMatches = this.cellMatches(notebookDocument, cell);
        if (syncInfo !== undefined) {
            const cellIsSynced = syncInfo.uris.has(cell.document.uri.toString());
            if ((cellMatches && cellIsSynced) || (!cellMatches && !cellIsSynced)) {
                // The cell doesn't match and was not synced or it matches and is synced.
                // In both cases nothing to do.
                //
                // Note that if the language mode of a document changes we remove the
                // cell and add it back to update the language mode on the server side.
                return;
            }
            if (cellMatches) {
                // don't use cells from above since there might be more matching cells in the notebook
                // Since we had a matching cell above we will have matching cells now.
                const matchingCells = this.getMatchingCells(notebookDocument);
                if (matchingCells !== undefined) {
                    const event = this.asNotebookDocumentChangeEvent(notebookDocument, undefined, syncInfo, matchingCells);
                    if (event !== undefined) {
                        this.doSendChange(event, matchingCells).catch(() => { });
                    }
                }
            }
        }
        else {
            // No sync info. But we have a open event for the notebook document
            // itself. If the cell matches then we need to send an open with
            // exactly that cell.
            if (cellMatches) {
                this.doSendOpen(notebookDocument, [cell]).catch(() => { });
            }
        }
    }
    didChangeNotebookCellTextDocument(notebookDocument, event) {
        // No match with the selector
        if (vscode.languages.match(this.selector, event.document) === 0) {
            return;
        }
        this.doSendChange({
            notebook: notebookDocument,
            cells: { textContent: [event] }
        }, undefined).catch(() => { });
    }
    didCloseNotebookCellTextDocument(notebookDocument, cell) {
        const syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString());
        if (syncInfo === undefined) {
            // The notebook document got never synced. So it doesn't matter if a cell
            // document closes.
            return;
        }
        const cellUri = cell.document.uri;
        const index = syncInfo.cells.findIndex((item) => item.document.uri.toString() === cellUri.toString());
        if (index === -1) {
            // The cell never got synced or it got deleted and we now received the document
            // close event.
            return;
        }
        if (index === 0 && syncInfo.cells.length === 1) {
            // The last cell. Close the notebook document in the server.
            this.doSendClose(notebookDocument, syncInfo.cells).catch(() => { });
        }
        else {
            const newCells = syncInfo.cells.slice();
            const deleted = newCells.splice(index, 1);
            this.doSendChange({
                notebook: notebookDocument,
                cells: {
                    structure: {
                        array: { start: index, deleteCount: 1 },
                        didClose: deleted
                    }
                }
            }, newCells).catch(() => { });
        }
    }
    dispose() {
        for (const disposable of this.disposables) {
            disposable.dispose();
        }
    }
    didOpen(notebookDocument, matchingCells = this.getMatchingCells(notebookDocument), syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString())) {
        if (syncInfo !== undefined) {
            if (matchingCells !== undefined) {
                const event = this.asNotebookDocumentChangeEvent(notebookDocument, undefined, syncInfo, matchingCells);
                if (event !== undefined) {
                    this.doSendChange(event, matchingCells).catch(() => { });
                }
            }
            else {
                this.doSendClose(notebookDocument, []).catch(() => { });
            }
        }
        else {
            // Check if we need to sync the notebook document.
            if (matchingCells === undefined) {
                return;
            }
            this.doSendOpen(notebookDocument, matchingCells).catch(() => { });
        }
    }
    didChangeNotebookDocument(event) {
        const notebookDocument = event.notebook;
        const syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString());
        if (syncInfo === undefined) {
            // We have no changes to the cells. Since the notebook wasn't synced
            // it will not be synced now.
            if (event.contentChanges.length === 0) {
                return;
            }
            // Check if we have new matching cells.
            const cells = this.getMatchingCells(notebookDocument);
            // No matching cells and the notebook never synced. So still no need
            // to sync it.
            if (cells === undefined) {
                return;
            }
            // Open the notebook document and ignore the rest of the changes
            // this the notebooks will be synced with the correct settings.
            this.didOpen(notebookDocument, cells, syncInfo);
        }
        else {
            // The notebook is synced. First check if we have no matching
            // cells anymore and if so close the notebook
            const cells = this.getMatchingCells(notebookDocument);
            if (cells === undefined) {
                this.didClose(notebookDocument, syncInfo);
                return;
            }
            const newEvent = this.asNotebookDocumentChangeEvent(event.notebook, event, syncInfo, cells);
            if (newEvent !== undefined) {
                this.doSendChange(newEvent, cells).catch(() => { });
            }
        }
    }
    didSave(notebookDocument) {
        const syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString());
        if (syncInfo === undefined) {
            return;
        }
        this.doSendSave(notebookDocument).catch(() => { });
    }
    didClose(notebookDocument, syncInfo = this.notebookSyncInfo.get(notebookDocument.uri.toString())) {
        if (syncInfo === undefined) {
            return;
        }
        const syncedCells = notebookDocument.getCells().filter(cell => syncInfo.uris.has(cell.document.uri.toString()));
        this.doSendClose(notebookDocument, syncedCells).catch(() => { });
    }
    async sendDidOpenNotebookDocument(notebookDocument) {
        const cells = this.getMatchingCells(notebookDocument);
        if (cells === undefined) {
            return;
        }
        return this.doSendOpen(notebookDocument, cells);
    }
    async doSendOpen(notebookDocument, cells) {
        const send = async (notebookDocument, cells) => {
            const nb = Converter.c2p.asNotebookDocument(notebookDocument, cells, this.client.code2ProtocolConverter);
            const cellDocuments = cells.map(cell => this.client.code2ProtocolConverter.asTextDocumentItem(cell.document));
            try {
                await this.client.sendNotification(proto.DidOpenNotebookDocumentNotification.type, {
                    notebookDocument: nb,
                    cellTextDocuments: cellDocuments
                });
            }
            catch (error) {
                this.client.error('Sending DidOpenNotebookDocumentNotification failed', error);
                throw error;
            }
        };
        const middleware = this.client.middleware?.notebooks;
        this.notebookSyncInfo.set(notebookDocument.uri.toString(), SyncInfo.create(cells));
        return middleware?.didOpen !== undefined ? middleware.didOpen(notebookDocument, cells, send) : send(notebookDocument, cells);
    }
    async sendDidChangeNotebookDocument(event) {
        return this.doSendChange(event, undefined);
    }
    async doSendChange(event, cells = this.getMatchingCells(event.notebook)) {
        const send = async (event) => {
            try {
                await this.client.sendNotification(proto.DidChangeNotebookDocumentNotification.type, {
                    notebookDocument: Converter.c2p.asVersionedNotebookDocumentIdentifier(event.notebook, this.client.code2ProtocolConverter),
                    change: Converter.c2p.asNotebookDocumentChangeEvent(event, this.client.code2ProtocolConverter)
                });
            }
            catch (error) {
                this.client.error('Sending DidChangeNotebookDocumentNotification failed', error);
                throw error;
            }
        };
        const middleware = this.client.middleware?.notebooks;
        if (event.cells?.structure !== undefined) {
            this.notebookSyncInfo.set(event.notebook.uri.toString(), SyncInfo.create(cells ?? []));
        }
        return middleware?.didChange !== undefined ? middleware?.didChange(event, send) : send(event);
    }
    async sendDidSaveNotebookDocument(notebookDocument) {
        return this.doSendSave(notebookDocument);
    }
    async doSendSave(notebookDocument) {
        const send = async (notebookDocument) => {
            try {
                await this.client.sendNotification(proto.DidSaveNotebookDocumentNotification.type, {
                    notebookDocument: { uri: this.client.code2ProtocolConverter.asUri(notebookDocument.uri) }
                });
            }
            catch (error) {
                this.client.error('Sending DidSaveNotebookDocumentNotification failed', error);
                throw error;
            }
        };
        const middleware = this.client.middleware?.notebooks;
        return middleware?.didSave !== undefined ? middleware.didSave(notebookDocument, send) : send(notebookDocument);
    }
    async sendDidCloseNotebookDocument(notebookDocument) {
        return this.doSendClose(notebookDocument, this.getMatchingCells(notebookDocument) ?? []);
    }
    async doSendClose(notebookDocument, cells) {
        const send = async (notebookDocument, cells) => {
            try {
                await this.client.sendNotification(proto.DidCloseNotebookDocumentNotification.type, {
                    notebookDocument: { uri: this.client.code2ProtocolConverter.asUri(notebookDocument.uri) },
                    cellTextDocuments: cells.map(cell => this.client.code2ProtocolConverter.asTextDocumentIdentifier(cell.document))
                });
            }
            catch (error) {
                this.client.error('Sending DidCloseNotebookDocumentNotification failed', error);
                throw error;
            }
        };
        const middleware = this.client.middleware?.notebooks;
        this.notebookSyncInfo.delete(notebookDocument.uri.toString());
        return middleware?.didClose !== undefined ? middleware.didClose(notebookDocument, cells, send) : send(notebookDocument, cells);
    }
    asNotebookDocumentChangeEvent(notebook, event, syncInfo, matchingCells) {
        if (event !== undefined && event.notebook !== notebook) {
            throw new Error('Notebook must be identical');
        }
        const result = {
            notebook: notebook
        };
        if (event?.metadata !== undefined) {
            result.metadata = Converter.c2p.asMetadata(event.metadata);
        }
        let matchingCellsSet;
        if (event?.cellChanges !== undefined && event.cellChanges.length > 0) {
            const data = [];
            // Only consider the new matching cells.
            matchingCellsSet = new Set(matchingCells.map(cell => cell.document.uri.toString()));
            for (const cellChange of event.cellChanges) {
                if (matchingCellsSet.has(cellChange.cell.document.uri.toString()) && (cellChange.executionSummary !== undefined || cellChange.metadata !== undefined)) {
                    data.push(cellChange.cell);
                }
            }
            if (data.length > 0) {
                result.cells = result.cells ?? {};
                result.cells.data = data;
            }
        }
        if (((event?.contentChanges !== undefined && event.contentChanges.length > 0) || event === undefined) && syncInfo !== undefined && matchingCells !== undefined) {
            // We still have matching cells. Check if the cell changes
            // affect the notebook on the server side.
            const oldCells = syncInfo.cells;
            const newCells = matchingCells;
            // meta data changes are reported using on the cell itself. So we can ignore comparing
            // it which has a positive effect on performance.
            const diff = $NotebookCell.computeDiff(oldCells, newCells, false);
            let addedCells;
            let removedCells;
            if (diff !== undefined) {
                addedCells = diff.cells === undefined
                    ? new Map()
                    : new Map(diff.cells.map(cell => [cell.document.uri.toString(), cell]));
                removedCells = diff.deleteCount === 0
                    ? new Map()
                    : new Map(oldCells.slice(diff.start, diff.start + diff.deleteCount).map(cell => [cell.document.uri.toString(), cell]));
                // Remove the onces that got deleted and inserted again.
                for (const key of Array.from(removedCells.keys())) {
                    if (addedCells.has(key)) {
                        removedCells.delete(key);
                        addedCells.delete(key);
                    }
                }
                result.cells = result.cells ?? {};
                const didOpen = [];
                const didClose = [];
                if (addedCells.size > 0 || removedCells.size > 0) {
                    for (const cell of addedCells.values()) {
                        didOpen.push(cell);
                    }
                    for (const cell of removedCells.values()) {
                        didClose.push(cell);
                    }
                }
                result.cells.structure = {
                    array: diff,
                    didOpen,
                    didClose
                };
            }
        }
        // The notebook is a property as well.
        return Object.keys(result).length > 1 ? result : undefined;
    }
    getMatchingCells(notebookDocument, cells = notebookDocument.getCells()) {
        if (this.options.notebookSelector === undefined) {
            return undefined;
        }
        for (const item of this.options.notebookSelector) {
            if (item.notebook === undefined || $NotebookDocumentFilter.matchNotebook(item.notebook, notebookDocument)) {
                const filtered = this.filterCells(notebookDocument, cells, item.cells);
                return filtered.length === 0 ? undefined : filtered;
            }
        }
        return undefined;
    }
    cellMatches(notebookDocument, cell) {
        const cells = this.getMatchingCells(notebookDocument, [cell]);
        return cells !== undefined && cells[0] === cell;
    }
    filterCells(notebookDocument, cells, cellSelector) {
        const filtered = cellSelector !== undefined ? cells.filter((cell) => {
            const cellLanguage = cell.document.languageId;
            return cellSelector.some((filter => (filter.language === '*' || cellLanguage === filter.language)));
        }) : cells;
        return typeof this.client.clientOptions.notebookDocumentOptions?.filterCells === 'function'
            ? this.client.clientOptions.notebookDocumentOptions.filterCells(notebookDocument, filtered)
            : filtered;
    }
}
class NotebookDocumentSyncFeature {
    constructor(client) {
        this.client = client;
        this.registrations = new Map();
        this.registrationType = proto.NotebookDocumentSyncRegistrationType.type;
        // We don't receive an event for cells where the document changes its language mode
        // Since we allow servers to filter on the language mode we fire such an event ourselves.
        vscode.workspace.onDidOpenTextDocument((textDocument) => {
            if (textDocument.uri.scheme !== NotebookDocumentSyncFeature.CellScheme) {
                return;
            }
            const [notebookDocument, notebookCell] = this.findNotebookDocumentAndCell(textDocument);
            if (notebookDocument === undefined || notebookCell === undefined) {
                return;
            }
            for (const provider of this.registrations.values()) {
                if (provider instanceof NotebookDocumentSyncFeatureProvider) {
                    provider.didOpenNotebookCellTextDocument(notebookDocument, notebookCell);
                }
            }
        });
        vscode.workspace.onDidChangeTextDocument((event) => {
            if (event.contentChanges.length === 0) {
                return;
            }
            const textDocument = event.document;
            if (textDocument.uri.scheme !== NotebookDocumentSyncFeature.CellScheme) {
                return;
            }
            const [notebookDocument,] = this.findNotebookDocumentAndCell(textDocument);
            if (notebookDocument === undefined) {
                return;
            }
            for (const provider of this.registrations.values()) {
                if (provider instanceof NotebookDocumentSyncFeatureProvider) {
                    provider.didChangeNotebookCellTextDocument(notebookDocument, event);
                }
            }
        });
        vscode.workspace.onDidCloseTextDocument((textDocument) => {
            if (textDocument.uri.scheme !== NotebookDocumentSyncFeature.CellScheme) {
                return;
            }
            // There are two cases when we receive a close for a text document
            // 1: the cell got removed. This is handled in `onDidChangeNotebookCells`
            // 2: the language mode of a cell changed. This keeps the URI stable so
            //    we will still find the cell and the notebook document.
            const [notebookDocument, notebookCell] = this.findNotebookDocumentAndCell(textDocument);
            if (notebookDocument === undefined || notebookCell === undefined) {
                return;
            }
            for (const provider of this.registrations.values()) {
                if (provider instanceof NotebookDocumentSyncFeatureProvider) {
                    provider.didCloseNotebookCellTextDocument(notebookDocument, notebookCell);
                }
            }
        });
    }
    getState() {
        if (this.registrations.size === 0) {
            return { kind: 'document', id: this.registrationType.method, registrations: false, matches: false };
        }
        for (const provider of this.registrations.values()) {
            const state = provider.getState();
            if (state.kind === 'document' && state.registrations === true && state.matches === true) {
                return { kind: 'document', id: this.registrationType.method, registrations: true, matches: true };
            }
        }
        return { kind: 'document', id: this.registrationType.method, registrations: true, matches: false };
    }
    fillClientCapabilities(capabilities) {
        const synchronization = ensure(ensure(capabilities, 'notebookDocument'), 'synchronization');
        synchronization.dynamicRegistration = true;
        synchronization.executionSummarySupport = true;
    }
    preInitialize(capabilities) {
        const options = capabilities.notebookDocumentSync;
        if (options === undefined) {
            return;
        }
        this.dedicatedChannel = this.client.protocol2CodeConverter.asDocumentSelector($NotebookDocumentSyncOptions.asDocumentSelector(options));
    }
    initialize(capabilities) {
        const options = capabilities.notebookDocumentSync;
        if (options === undefined) {
            return;
        }
        const id = options.id ?? UUID.generateUuid();
        this.register({ id, registerOptions: options });
    }
    register(data) {
        const provider = new NotebookDocumentSyncFeatureProvider(this.client, data.registerOptions);
        this.registrations.set(data.id, provider);
    }
    unregister(id) {
        const provider = this.registrations.get(id);
        provider && provider.dispose();
    }
    dispose() {
        for (const provider of this.registrations.values()) {
            provider.dispose();
        }
        this.registrations.clear();
    }
    handles(textDocument) {
        if (textDocument.uri.scheme !== NotebookDocumentSyncFeature.CellScheme) {
            return false;
        }
        if (this.dedicatedChannel !== undefined && vscode.languages.match(this.dedicatedChannel, textDocument) > 0) {
            return true;
        }
        for (const provider of this.registrations.values()) {
            if (provider.handles(textDocument)) {
                return true;
            }
        }
        return false;
    }
    getProvider(notebookCell) {
        for (const provider of this.registrations.values()) {
            if (provider.handles(notebookCell.document)) {
                return provider;
            }
        }
        return undefined;
    }
    findNotebookDocumentAndCell(textDocument) {
        const uri = textDocument.uri.toString();
        for (const notebookDocument of vscode.workspace.notebookDocuments) {
            for (const cell of notebookDocument.getCells()) {
                if (cell.document.uri.toString() === uri) {
                    return [notebookDocument, cell];
                }
            }
        }
        return [undefined, undefined];
    }
}
exports.NotebookDocumentSyncFeature = NotebookDocumentSyncFeature;
NotebookDocumentSyncFeature.CellScheme = 'vscode-notebook-cell';
