import { __decorate, __param } from '../../../../../../external/tslib/tslib.es6.js';
import { timeout, IntervalTimer } from '../../../base/common/async.js';
import { Disposable, dispose, toDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { SimpleWorkerClient, logOnceWebWorkerWarning } from '../../../base/common/worker/simpleWorker.js';
import { DefaultWorkerFactory } from '../../../base/browser/defaultWorkerFactory.js';
import { Range } from '../../common/core/range.js';
import { ILanguageConfigurationService } from '../../common/languages/languageConfigurationRegistry.js';
import { EditorSimpleWorker } from '../../common/services/editorSimpleWorker.js';
import { IModelService } from '../../common/services/model.js';
import { ITextResourceConfigurationService } from '../../common/services/textResourceConfiguration.js';
import { isNonEmptyArray } from '../../../base/common/arrays.js';
import { ILogService } from '../../../platform/log/common/log.js';
import { StopWatch } from '../../../base/common/stopwatch.js';
import { canceled, onUnexpectedError } from '../../../base/common/errors.js';
import { ILanguageFeaturesService } from '../../common/services/languageFeatures.js';
import { MovedText } from '../../common/diff/linesDiffComputer.js';
import { LineRangeMapping, DetailedLineRangeMapping, RangeMapping } from '../../common/diff/rangeMapping.js';
import { LineRange } from '../../common/core/lineRange.js';
import { mainWindow } from '../../../base/browser/window.js';
import { WindowIntervalTimer } from '../../../base/browser/dom.js';
const STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000;
const STOP_WORKER_DELTA_TIME_MS = 5 * 60 * 1000;
function canSyncModel(modelService, resource) {
    const model = modelService.getModel(resource);
    if (!model) {
        return false;
    }
    if (model.isTooLargeForSyncing()) {
        return false;
    }
    return true;
}
let EditorWorkerService = class EditorWorkerService extends Disposable {
    constructor(modelService, configurationService, logService, languageConfigurationService, languageFeaturesService) {
        super();
        this._modelService = modelService;
        this._workerManager = this._register(( new WorkerManager(this._modelService, languageConfigurationService)));
        this._logService = logService;
        this._register(languageFeaturesService.linkProvider.register({ language: '*', hasAccessToAllModels: true }, {
            provideLinks: (model, token) => {
                if (!canSyncModel(this._modelService, model.uri)) {
                    return Promise.resolve({ links: [] });
                }
                return this._workerManager.withWorker().then(client => client.computeLinks(model.uri)).then(links => {
                    return links && { links };
                });
            }
        }));
        this._register(languageFeaturesService.completionProvider.register('*', ( new WordBasedCompletionItemProvider(
            this._workerManager,
            configurationService,
            this._modelService,
            languageConfigurationService
        ))));
    }
    dispose() {
        super.dispose();
    }
    canComputeUnicodeHighlights(uri) {
        return canSyncModel(this._modelService, uri);
    }
    computedUnicodeHighlights(uri, options, range) {
        return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range));
    }
    async computeDiff(original, modified, options, algorithm) {
        const result = await this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options, algorithm));
        if (!result) {
            return null;
        }
        const diff = {
            identical: result.identical,
            quitEarly: result.quitEarly,
            changes: toLineRangeMappings(result.changes),
            moves: ( result.moves.map(m => ( new MovedText(( new LineRangeMapping(( new LineRange(m[0], m[1])), ( new LineRange(m[2], m[3])))), toLineRangeMappings(m[4])))))
        };
        return diff;
        function toLineRangeMappings(changes) {
            return ( changes.map((c) => ( new DetailedLineRangeMapping(( new LineRange(c[0], c[1])), ( new LineRange(c[2], c[3])), c[4]?.map((c) => ( new RangeMapping(( new Range(c[0], c[1], c[2], c[3])), ( new Range(c[4], c[5], c[6], c[7])))))))));
        }
    }
    canComputeDirtyDiff(original, modified) {
        return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified));
    }
    computeDirtyDiff(original, modified, ignoreTrimWhitespace) {
        return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace));
    }
    computeMoreMinimalEdits(resource, edits, pretty = false) {
        if (isNonEmptyArray(edits)) {
            if (!canSyncModel(this._modelService, resource)) {
                return Promise.resolve(edits);
            }
            const sw = StopWatch.create();
            const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits, pretty));
            result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', ( resource.toString(true)), sw.elapsed()));
            return Promise.race([result, timeout(1000).then(() => edits)]);
        }
        else {
            return Promise.resolve(undefined);
        }
    }
    computeHumanReadableDiff(resource, edits) {
        if (isNonEmptyArray(edits)) {
            if (!canSyncModel(this._modelService, resource)) {
                return Promise.resolve(edits);
            }
            const sw = StopWatch.create();
            const result = this._workerManager.withWorker().then(client => client.computeHumanReadableDiff(resource, edits, { ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false, })).catch((err) => {
                onUnexpectedError(err);
                return this.computeMoreMinimalEdits(resource, edits, true);
            });
            result.finally(() => this._logService.trace('FORMAT#computeHumanReadableDiff', ( resource.toString(true)), sw.elapsed()));
            return result;
        }
        else {
            return Promise.resolve(undefined);
        }
    }
    canNavigateValueSet(resource) {
        return (canSyncModel(this._modelService, resource));
    }
    navigateValueSet(resource, range, up) {
        return this._workerManager.withWorker().then(client => client.navigateValueSet(resource, range, up));
    }
    canComputeWordRanges(resource) {
        return canSyncModel(this._modelService, resource);
    }
    computeWordRanges(resource, range) {
        return this._workerManager.withWorker().then(client => client.computeWordRanges(resource, range));
    }
    findSectionHeaders(uri, options) {
        return this._workerManager.withWorker().then(client => client.findSectionHeaders(uri, options));
    }
};
EditorWorkerService = ( __decorate([
    ( __param(0, IModelService)),
    ( __param(1, ITextResourceConfigurationService)),
    ( __param(2, ILogService)),
    ( __param(3, ILanguageConfigurationService)),
    ( __param(4, ILanguageFeaturesService))
], EditorWorkerService));
class WordBasedCompletionItemProvider {
    constructor(workerManager, configurationService, modelService, languageConfigurationService) {
        this.languageConfigurationService = languageConfigurationService;
        this._debugDisplayName = 'wordbasedCompletions';
        this._workerManager = workerManager;
        this._configurationService = configurationService;
        this._modelService = modelService;
    }
    async provideCompletionItems(model, position) {
        const config = this._configurationService.getValue(model.uri, position, 'editor');
        if (config.wordBasedSuggestions === 'off') {
            return undefined;
        }
        const models = [];
        if (config.wordBasedSuggestions === 'currentDocument') {
            if (canSyncModel(this._modelService, model.uri)) {
                models.push(model.uri);
            }
        }
        else {
            for (const candidate of this._modelService.getModels()) {
                if (!canSyncModel(this._modelService, candidate.uri)) {
                    continue;
                }
                if (candidate === model) {
                    models.unshift(candidate.uri);
                }
                else if (config.wordBasedSuggestions === 'allDocuments' || candidate.getLanguageId() === model.getLanguageId()) {
                    models.push(candidate.uri);
                }
            }
        }
        if (models.length === 0) {
            return undefined;
        }
        const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
        const word = model.getWordAtPosition(position);
        const replace = !word ? Range.fromPositions(position) : ( new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn));
        const insert = replace.setEndPosition(position.lineNumber, position.column);
        const client = await this._workerManager.withWorker();
        const data = await client.textualSuggest(models, word?.word, wordDefRegExp);
        if (!data) {
            return undefined;
        }
        return {
            duration: data.duration,
            suggestions: ( data.words.map((word) => {
                return {
                    kind: 18 ,
                    label: word,
                    insertText: word,
                    range: { insert, replace }
                };
            })),
        };
    }
}
class WorkerManager extends Disposable {
    constructor(modelService, languageConfigurationService) {
        super();
        this.languageConfigurationService = languageConfigurationService;
        this._modelService = modelService;
        this._editorWorkerClient = null;
        this._lastWorkerUsedTime = (( new Date())).getTime();
        const stopWorkerInterval = this._register(( new WindowIntervalTimer()));
        stopWorkerInterval.cancelAndSet(() => this._checkStopIdleWorker(), Math.round(STOP_WORKER_DELTA_TIME_MS / 2), mainWindow);
        this._register(this._modelService.onModelRemoved(_ => this._checkStopEmptyWorker()));
    }
    dispose() {
        if (this._editorWorkerClient) {
            this._editorWorkerClient.dispose();
            this._editorWorkerClient = null;
        }
        super.dispose();
    }
    _checkStopEmptyWorker() {
        if (!this._editorWorkerClient) {
            return;
        }
        const models = this._modelService.getModels();
        if (models.length === 0) {
            this._editorWorkerClient.dispose();
            this._editorWorkerClient = null;
        }
    }
    _checkStopIdleWorker() {
        if (!this._editorWorkerClient) {
            return;
        }
        const timeSinceLastWorkerUsedTime = (( new Date())).getTime() - this._lastWorkerUsedTime;
        if (timeSinceLastWorkerUsedTime > STOP_WORKER_DELTA_TIME_MS) {
            this._editorWorkerClient.dispose();
            this._editorWorkerClient = null;
        }
    }
    withWorker() {
        this._lastWorkerUsedTime = (( new Date())).getTime();
        if (!this._editorWorkerClient) {
            this._editorWorkerClient = ( new EditorWorkerClient(
                this._modelService,
                false,
                'editorWorkerService',
                this.languageConfigurationService
            ));
        }
        return Promise.resolve(this._editorWorkerClient);
    }
}
class EditorModelManager extends Disposable {
    constructor(proxy, modelService, keepIdleModels) {
        super();
        this._syncedModels = Object.create(null);
        this._syncedModelsLastUsedTime = Object.create(null);
        this._proxy = proxy;
        this._modelService = modelService;
        if (!keepIdleModels) {
            const timer = ( new IntervalTimer());
            timer.cancelAndSet(() => this._checkStopModelSync(), Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2));
            this._register(timer);
        }
    }
    dispose() {
        for (const modelUrl in this._syncedModels) {
            dispose(this._syncedModels[modelUrl]);
        }
        this._syncedModels = Object.create(null);
        this._syncedModelsLastUsedTime = Object.create(null);
        super.dispose();
    }
    ensureSyncedResources(resources, forceLargeModels) {
        for (const resource of resources) {
            const resourceStr = ( resource.toString());
            if (!this._syncedModels[resourceStr]) {
                this._beginModelSync(resource, forceLargeModels);
            }
            if (this._syncedModels[resourceStr]) {
                this._syncedModelsLastUsedTime[resourceStr] = (( new Date())).getTime();
            }
        }
    }
    _checkStopModelSync() {
        const currentTime = (( new Date())).getTime();
        const toRemove = [];
        for (const modelUrl in this._syncedModelsLastUsedTime) {
            const elapsedTime = currentTime - this._syncedModelsLastUsedTime[modelUrl];
            if (elapsedTime > STOP_SYNC_MODEL_DELTA_TIME_MS) {
                toRemove.push(modelUrl);
            }
        }
        for (const e of toRemove) {
            this._stopModelSync(e);
        }
    }
    _beginModelSync(resource, forceLargeModels) {
        const model = this._modelService.getModel(resource);
        if (!model) {
            return;
        }
        if (!forceLargeModels && model.isTooLargeForSyncing()) {
            return;
        }
        const modelUrl = ( resource.toString());
        this._proxy.acceptNewModel({
            url: ( model.uri.toString()),
            lines: model.getLinesContent(),
            EOL: model.getEOL(),
            versionId: model.getVersionId()
        });
        const toDispose = ( new DisposableStore());
        toDispose.add(model.onDidChangeContent((e) => {
            this._proxy.acceptModelChanged(( modelUrl.toString()), e);
        }));
        toDispose.add(model.onWillDispose(() => {
            this._stopModelSync(modelUrl);
        }));
        toDispose.add(toDisposable(() => {
            this._proxy.acceptRemovedModel(modelUrl);
        }));
        this._syncedModels[modelUrl] = toDispose;
    }
    _stopModelSync(modelUrl) {
        const toDispose = this._syncedModels[modelUrl];
        delete this._syncedModels[modelUrl];
        delete this._syncedModelsLastUsedTime[modelUrl];
        dispose(toDispose);
    }
}
class SynchronousWorkerClient {
    constructor(instance) {
        this._instance = instance;
        this._proxyObj = Promise.resolve(this._instance);
    }
    dispose() {
        this._instance.dispose();
    }
    getProxyObject() {
        return this._proxyObj;
    }
}
class EditorWorkerHost {
    constructor(workerClient) {
        this._workerClient = workerClient;
    }
    fhr(method, args) {
        return this._workerClient.fhr(method, args);
    }
}
class EditorWorkerClient extends Disposable {
    constructor(modelService, keepIdleModels, label, languageConfigurationService) {
        super();
        this.languageConfigurationService = languageConfigurationService;
        this._disposed = false;
        this._modelService = modelService;
        this._keepIdleModels = keepIdleModels;
        this._workerFactory = ( new DefaultWorkerFactory(label));
        this._worker = null;
        this._modelManager = null;
    }
    fhr(method, args) {
        throw new Error(`Not implemented!`);
    }
    _getOrCreateWorker() {
        if (!this._worker) {
            try {
                this._worker = this._register(( new SimpleWorkerClient(
                    this._workerFactory,
                    'vs/editor/common/services/editorSimpleWorker',
                    ( new EditorWorkerHost(this))
                )));
            }
            catch (err) {
                logOnceWebWorkerWarning(err);
                this._worker = ( new SynchronousWorkerClient(( new EditorSimpleWorker(( new EditorWorkerHost(this)), null))));
            }
        }
        return this._worker;
    }
    _getProxy() {
        return this._getOrCreateWorker().getProxyObject().then(undefined, (err) => {
            logOnceWebWorkerWarning(err);
            this._worker = ( new SynchronousWorkerClient(( new EditorSimpleWorker(( new EditorWorkerHost(this)), null))));
            return this._getOrCreateWorker().getProxyObject();
        });
    }
    _getOrCreateModelManager(proxy) {
        if (!this._modelManager) {
            this._modelManager = this._register(( new EditorModelManager(proxy, this._modelService, this._keepIdleModels)));
        }
        return this._modelManager;
    }
    async _withSyncedResources(resources, forceLargeModels = false) {
        if (this._disposed) {
            return Promise.reject(canceled());
        }
        return this._getProxy().then((proxy) => {
            this._getOrCreateModelManager(proxy).ensureSyncedResources(resources, forceLargeModels);
            return proxy;
        });
    }
    computedUnicodeHighlights(uri, options, range) {
        return this._withSyncedResources([uri]).then(proxy => {
            return proxy.computeUnicodeHighlights(( uri.toString()), options, range);
        });
    }
    computeDiff(original, modified, options, algorithm) {
        return this._withSyncedResources([original, modified],  true).then(proxy => {
            return proxy.computeDiff(( original.toString()), ( modified.toString()), options, algorithm);
        });
    }
    computeDirtyDiff(original, modified, ignoreTrimWhitespace) {
        return this._withSyncedResources([original, modified]).then(proxy => {
            return proxy.computeDirtyDiff(( original.toString()), ( modified.toString()), ignoreTrimWhitespace);
        });
    }
    computeMoreMinimalEdits(resource, edits, pretty) {
        return this._withSyncedResources([resource]).then(proxy => {
            return proxy.computeMoreMinimalEdits(( resource.toString()), edits, pretty);
        });
    }
    computeHumanReadableDiff(resource, edits, options) {
        return this._withSyncedResources([resource]).then(proxy => {
            return proxy.computeHumanReadableDiff(( resource.toString()), edits, options);
        });
    }
    computeLinks(resource) {
        return this._withSyncedResources([resource]).then(proxy => {
            return proxy.computeLinks(( resource.toString()));
        });
    }
    computeDefaultDocumentColors(resource) {
        return this._withSyncedResources([resource]).then(proxy => {
            return proxy.computeDefaultDocumentColors(( resource.toString()));
        });
    }
    async textualSuggest(resources, leadingWord, wordDefRegExp) {
        const proxy = await this._withSyncedResources(resources);
        const wordDef = wordDefRegExp.source;
        const wordDefFlags = wordDefRegExp.flags;
        return proxy.textualSuggest(( resources.map(r => ( r.toString()))), leadingWord, wordDef, wordDefFlags);
    }
    computeWordRanges(resource, range) {
        return this._withSyncedResources([resource]).then(proxy => {
            const model = this._modelService.getModel(resource);
            if (!model) {
                return Promise.resolve(null);
            }
            const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
            const wordDef = wordDefRegExp.source;
            const wordDefFlags = wordDefRegExp.flags;
            return proxy.computeWordRanges(( resource.toString()), range, wordDef, wordDefFlags);
        });
    }
    navigateValueSet(resource, range, up) {
        return this._withSyncedResources([resource]).then(proxy => {
            const model = this._modelService.getModel(resource);
            if (!model) {
                return null;
            }
            const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
            const wordDef = wordDefRegExp.source;
            const wordDefFlags = wordDefRegExp.flags;
            return proxy.navigateValueSet(( resource.toString()), range, up, wordDef, wordDefFlags);
        });
    }
    findSectionHeaders(uri, options) {
        return this._withSyncedResources([uri]).then(proxy => {
            return proxy.findSectionHeaders(( uri.toString()), options);
        });
    }
    dispose() {
        super.dispose();
        this._disposed = true;
    }
}
export { EditorWorkerClient, EditorWorkerHost, EditorWorkerService };
