/* tslint:disable */
import { onUnexpectedError } from '@vscode/monaco-editor/esm/vs/base/common/errors';
import { IDisposable } from '@vscode/monaco-editor/esm/vs/base/common/lifecycle';
import { URI, UriComponents } from '@vscode/monaco-editor/esm/vs/base/common/uri';
import { EditOperation } from '@vscode/monaco-editor/esm/vs/editor/common/core/editOperation';
import { CancellationTokenSource } from '@vscode/monaco-editor/esm/vs/base/common/cancellation';
import { Range } from '@vscode/monaco-editor/esm/vs/editor/common/core/range';
import { ExtHostContext, MainThreadDocumentContentProvidersShape, IExtHostContext, MainContext } from '@workbench-stack/extension-api/lib/protocol/server.protocol';
import { ExtHostDocumentContentProvidersShape } from '@workbench-stack/extension-api/lib/protocol/common.protocol'
import { IModelService } from '@vscode/monaco-editor/esm/vs/editor/common/services/modelService';
import { IEditorWorkerService } from '@vscode/monaco-editor/esm/vs/editor/common/services/editorWorkerService';
import { EditorWorkerServiceImpl } from '@vscode/monaco-editor/esm/vs/editor/common/services/editorWorkerServiceImpl';
import { StaticServices } from '@vscode/monaco-editor/esm/vs/editor/standalone/browser/standaloneServices';
import { ITextModel } from '@vscode/monaco-editor/esm/vs/editor/common/model';
import { IModeService } from '@vscode/monaco-editor/esm/vs/editor/common/services/modeService';

export class MainThreadDocumentContentProviders implements MainThreadDocumentContentProvidersShape {

    private readonly _resourceContentProvider = new Map<number, IDisposable>();
    private readonly _pendingUpdate = new Map<string, CancellationTokenSource>();
    private readonly _proxy: ExtHostDocumentContentProvidersShape;

    private readonly _editorWorkerService: IEditorWorkerService;
    private readonly _modeService: IModeService
    constructor(
        extHostContext: IExtHostContext,
        private readonly _modelService: IModelService,
        private _textModelResolverService,
    ) {
        this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentContentProviders);        
    }

    dispose(): void {
        this._resourceContentProvider.forEach(p => p.dispose());
        this._pendingUpdate.forEach(source => source.dispose());
    }

    $registerTextContentProvider(handle: number, scheme: string): void {
        const registration = this._textModelResolverService.registerTextModelContentProvider(scheme, {
            provideTextContent: (uri: URI): Promise<ITextModel> => {
                return this._proxy.$provideTextDocumentContent(handle, uri).then(value => {
                    if (typeof value === 'string') {
                        const firstLineText = value.substr(0, 1 + value.search(/\r?\n/));
                        const languageSelection = StaticServices.modeService.get().createByFilepathOrFirstLine(uri.fsPath, firstLineText);
                        return this._modelService.createModel(value, languageSelection, uri);
                    }
                    return undefined;
                })
            }
        });
        this._resourceContentProvider.set(handle, registration);
    }

    $unregisterTextContentProvider(handle: number): void {
		const registration = this._resourceContentProvider.get(handle);
		if (registration) {
			registration.dispose();
			this._resourceContentProvider.delete(handle);
		}
    }

    $onVirtualDocumentChange(uri: UriComponents, value: string): void {
        // console.warn('not yet implemented');
        const model = this._modelService.getModel(URI.revive(uri));
        if (!model) {
            return;
        }

        // cancle and dispose an existing update
        if(this._pendingUpdate.has(model.id)) {
            this._pendingUpdate.get(model.id).cancel();
        }

        // create and keep update token
        const myToken = new CancellationTokenSource();
        this._pendingUpdate.set(model.id, myToken);

		this._editorWorkerService.computeMoreMinimalEdits(model.uri, [{ text: value, range: model.getFullModelRange() }]).then(edits => {
			// remove token
			this._pendingUpdate.delete(model.id);

			if (myToken.token.isCancellationRequested) {
				// ignore this
				return;
			}
			if (edits.length > 0) {
				// use the evil-edit as these models show in readonly-editor only
				model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
			}
		}).catch(onUnexpectedError);

    }
}