"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createDocuments = exports.combineContinuousChangeRanges = void 0;
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
const vscode = require("vscode-languageserver");
const uriMap_1 = require("./utils/uriMap");
class IncrementalScriptSnapshot {
    constructor(uri, languageId, version, text) {
        this.uri = uri;
        this.document = vscode_languageserver_textdocument_1.TextDocument.create(uri, languageId, version, text);
        this.changes = [
            {
                applied: true,
                changeRange: undefined,
                version,
                contentChange: undefined,
                snapshot: undefined,
            }
        ];
    }
    get version() {
        return this.changes[this.changes.length - 1].version;
    }
    get languageId() {
        return this.document.languageId;
    }
    update(params) {
        vscode_languageserver_textdocument_1.TextDocument.update(this.document, params.contentChanges, params.textDocument.version);
        this.changes = [
            {
                applied: true,
                changeRange: undefined,
                version: params.textDocument.version,
                contentChange: undefined,
                snapshot: undefined,
            }
        ];
    }
    getSnapshot() {
        this.clearUnReferenceVersions();
        const lastChange = this.changes[this.changes.length - 1];
        if (!lastChange.snapshot) {
            this.applyVersionChanges(lastChange.version, false);
            const text = this.document.getText();
            const cache = new WeakMap();
            const snapshot = {
                getText: (start, end) => text.substring(start, end),
                getLength: () => text.length,
                getChangeRange: (oldSnapshot) => {
                    if (!cache.has(oldSnapshot)) {
                        const oldIndex = this.changes.findIndex(change => change.snapshot?.deref() === oldSnapshot);
                        if (oldIndex >= 0) {
                            const start = oldIndex + 1;
                            const end = this.changes.indexOf(lastChange) + 1;
                            const changeRanges = this.changes.slice(start, end).map(change => change.changeRange);
                            const result = combineContinuousChangeRanges.apply(null, changeRanges);
                            cache.set(oldSnapshot, result);
                        }
                        else {
                            cache.set(oldSnapshot, undefined);
                        }
                    }
                    return cache.get(oldSnapshot);
                },
            };
            lastChange.snapshot = new WeakRef(snapshot);
        }
        return lastChange.snapshot.deref();
    }
    getDocument() {
        this.clearUnReferenceVersions();
        const lastChange = this.changes[this.changes.length - 1];
        if (!lastChange.applied) {
            this.applyVersionChanges(lastChange.version, false);
        }
        return this.document;
    }
    clearUnReferenceVersions() {
        let versionToApply;
        for (let i = 0; i <= this.changes.length - 2; i++) {
            const change = this.changes[i];
            const nextChange = this.changes[i + 1];
            if (!change.snapshot?.deref()) {
                if (change.version !== nextChange.version) {
                    versionToApply = change.version;
                }
            }
            else {
                break;
            }
        }
        if (versionToApply !== undefined) {
            this.applyVersionChanges(versionToApply, true);
        }
    }
    applyVersionChanges(version, removeBeforeVersions) {
        let removeEnd = -1;
        for (let i = 0; i < this.changes.length; i++) {
            const change = this.changes[i];
            if (change.version > version) {
                break;
            }
            if (!change.applied) {
                if (change.contentChange) {
                    change.changeRange = {
                        span: {
                            start: this.document.offsetAt(change.contentChange.range.start),
                            length: this.document.offsetAt(change.contentChange.range.end) - this.document.offsetAt(change.contentChange.range.start),
                        },
                        newLength: change.contentChange.text.length,
                    };
                    vscode_languageserver_textdocument_1.TextDocument.update(this.document, [change.contentChange], change.version);
                }
                change.applied = true;
            }
            removeEnd = i + 1;
        }
        if (removeBeforeVersions && removeEnd >= 1) {
            this.changes.splice(0, removeEnd);
        }
    }
}
function combineContinuousChangeRanges(...changeRanges) {
    if (changeRanges.length === 1) {
        return changeRanges[0];
    }
    let changeRange = changeRanges[0];
    for (let i = 1; i < changeRanges.length; i++) {
        const nextChangeRange = changeRanges[i];
        changeRange = _combineContinuousChangeRanges(changeRange, nextChangeRange);
    }
    return changeRange;
}
exports.combineContinuousChangeRanges = combineContinuousChangeRanges;
// https://tsplay.dev/w6Paym - @browsnet
function _combineContinuousChangeRanges(a, b) {
    const aStart = a.span.start;
    const aEnd = a.span.start + a.span.length;
    const aDiff = a.newLength - a.span.length;
    const changeBegin = aStart + Math.min(a.span.length, a.newLength);
    const rollback = (start) => start > changeBegin ? start - aDiff : start;
    const bStart = rollback(b.span.start);
    const bEnd = rollback(b.span.start + b.span.length);
    const bDiff = b.newLength - b.span.length;
    const start = Math.min(aStart, bStart);
    const end = Math.max(aEnd, bEnd);
    const length = end - start;
    const newLength = aDiff + bDiff + length;
    return { span: { start, length }, newLength };
}
function createDocuments(env, connection) {
    const snapshots = (0, uriMap_1.createUriMap)(env.fileNameToUri);
    const onDidChangeContents = new Set();
    const onDidCloses = new Set();
    connection.onDidOpenTextDocument(params => {
        if (params.textDocument.uri.startsWith('git:/'))
            return;
        snapshots.uriSet(params.textDocument.uri, new IncrementalScriptSnapshot(params.textDocument.uri, params.textDocument.languageId, params.textDocument.version, params.textDocument.text));
        for (const cb of onDidChangeContents) {
            cb({ textDocument: params.textDocument, contentChanges: [{ text: params.textDocument.text }] });
        }
    });
    connection.onDidChangeTextDocument(params => {
        if (params.textDocument.uri.startsWith('git:/'))
            return;
        const incrementalSnapshot = snapshots.uriGet(params.textDocument.uri);
        if (incrementalSnapshot) {
            if (params.contentChanges.every(vscode.TextDocumentContentChangeEvent.isIncremental)) {
                for (const contentChange of params.contentChanges) {
                    incrementalSnapshot.changes.push({
                        applied: false,
                        changeRange: undefined,
                        contentChange,
                        version: params.textDocument.version,
                        snapshot: undefined,
                    });
                }
            }
            else {
                incrementalSnapshot.update(params);
            }
        }
        for (const cb of onDidChangeContents) {
            cb(params);
        }
    });
    connection.onDidCloseTextDocument(params => {
        if (params.textDocument.uri.startsWith('git:/'))
            return;
        snapshots.uriDelete(params.textDocument.uri);
        for (const cb of onDidCloses) {
            cb(params);
        }
    });
    return {
        data: snapshots,
        onDidChangeContent: (cb) => {
            onDidChangeContents.add(cb);
            return {
                dispose() {
                    onDidChangeContents.delete(cb);
                },
            };
        },
        onDidClose: (cb) => {
            onDidCloses.add(cb);
            return {
                dispose() {
                    onDidCloses.delete(cb);
                },
            };
        },
    };
}
exports.createDocuments = createDocuments;
//# sourceMappingURL=documents.js.map