import {
    WebExtHostLanguageFeaturesShape, ClientThreadLanguageFeaturesShape, ISerializedDocumentFilter,
    DefinitionLinkDto,
} from '@workbench-stack/extension-api';
import { Observable, Unsubscribable } from 'rxjs';
import * as cdecode from 'cdecode/client';
import { regExpLeadsToEndlessLoop } from '@vscode/monaco-editor/esm/vs/base/common/strings';
import { URI, UriComponents } from '@vscode/monaco-editor/esm/vs/base/common/uri';
import {
    ProviderMap, toProviderResultObservable, WorkspaceEditDto, SuggestResultDto, CodeActionDto,
    LinkDto, IRawColorInfo, ISerializedLanguageConfiguration, WorkspaceSymbolDto, WorkspaceSymbolsDto,
} from '@workbench-stack/extension-api';
import * as modes from '@vscode/monaco-editor/esm/vs/editor/common/modes';
import * as types from '@workbench-stack/extension-api/lib/core/types/ext-host-types';
import * as typeConvert from '@workbench-stack/extension-api/lib/core/converters/ext-host-type-converters';
import { CodeActionKind } from '@workbench-stack/extension-api/lib/core/types/ext-host-types';
import { ISelection, Selection } from '@vscode/monaco-editor/esm/vs/editor/common/core/selection';
import { IPosition } from '@vscode/monaco-editor/esm/vs/editor/common/core/position';
import { IRange } from '@vscode/monaco-editor/esm/vs/editor/common/core/range';
import { ExtHostDocuments } from './ext-host-documents';
import {
    toDocumentSymbols, convertToDefinitionLinks, _transformDocumentSelector, hoverAdapter,
    toDocumentHighlights, toReferences, toCodeLens, toTextEdit, toWorkspaceEdit,
    toCompletionItem, RenameAdapter_supportsResolving,
    _asMessage, RenameAdapter_resolveRenameLocation, resolveWorkspaceSymbol,
    toCodeLength, toSignatureHelp, toDocumentFormating, toCodeActions, SuggestAdapter, LinkProviderAdapter,
    provideColors, provideColorPresentations, provideWorkspaceSymbols, provideFoldingRanges,
} from './ext-host-language-helpers';
import { IExtensionDescription, ExtensionIdentifier } from '@workbench-stack/core';
import { ISingleEditOperation, EndOfLineSequence } from '@vscode/monaco-editor/esm/vs/editor/common/model';



export class WebExtHostLanguageFeatures implements WebExtHostLanguageFeatures, Unsubscribable {

    private registrations = new ProviderMap<
        | cdecode.HoverProvider
        | cdecode.DefinitionProvider
        | cdecode.TypeDefinitionProvider
        | cdecode.ImplementationProvider
        | cdecode.ReferenceProvider
        | cdecode.LocationProvider
        | cdecode.DocumentSymbolProvider
        | cdecode.DocumentHighlightProvider
        | cdecode.DocumentFormattingEditProvider
        | cdecode.CompletionItemProvider
        | cdecode.CodeActionProvider
        | cdecode.CodeLensProvider
        | cdecode.DocumentRangeFormattingEditProvider
        | cdecode.RenameProvider
        | cdecode.DeclarationProvider
        | cdecode.SignatureHelpProvider
        | cdecode.DocumentLinkProvider
        | cdecode.DocumentColorProvider
        | cdecode.OnTypeFormattingEditProvider
        | cdecode.FoldingRangeProvider
        | cdecode.WorkspaceSymbolProvider
        | string

    >(id => this.proxy.$unregister(id));

    private suggestAdapter = new SuggestAdapter();
    private linkAdapter = new LinkProviderAdapter();

    constructor(
        private proxy: ClientThreadLanguageFeaturesShape,
        private documents: ExtHostDocuments,
        // commands: ExtHostCommands,
        // heapMonitor: ExtHostHeapService,
        // diagnostics: ExtHostDi,
        // logService: ILogService,
    ) { }

    // --- outline

    public registerDocumentSymbolProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentSymbolProvider, extension?: IExtensionDescription): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerOutlineSupport(id, _transformDocumentSelector(selector), extension ? extension.displayName || extension.name : undefined);
        return subscription;
    }

    public $observeDocumentSymbols(
        handle: number,
        resource: UriComponents,
    ): Observable<modes.DocumentSymbol[] | undefined> {
        const provider = this.registrations.get<cdecode.DocumentSymbolProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[(cdecode.SymbolInformation[] | cdecode.DocumentSymbol[] | cdecode.Subscribable<cdecode.SymbolInformation[] | cdecode.DocumentSymbol[]>)]>(
                    document => {
                        return [
                            provider.provideDocumentSymbols(document) as (cdecode.SymbolInformation[] | cdecode.DocumentSymbol[] |
                                cdecode.Subscribable<cdecode.SymbolInformation[] | cdecode.DocumentSymbol[]>)];
                    }),
            toDocumentSymbols,
        );
    }

    // --- code lens
    public registerCodeLensProvider(selector: cdecode.DocumentSelector, provider: cdecode.CodeLensProvider, eventHandle: number): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerCodeLensSupport(id, _transformDocumentSelector(selector), eventHandle);
        return subscription;
    }

    public $observeCodeLenses(
        handle: number,
        resource: UriComponents,
    ): Observable<modes.ICodeLensSymbol[] | undefined> {
        const provider = this.registrations.get<cdecode.CodeLensProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[cdecode.CodeLens[] | cdecode.Subscribable<cdecode.CodeLens[] | undefined>]>(document =>
                    [provider.provideCodeLenses(document) as (cdecode.CodeLens[] | cdecode.Subscribable<cdecode.CodeLens[] | undefined>)]),
            toCodeLens,
        );
    }

    public $resolveCodeLens(
        handle: number,
        resource: UriComponents,
        codeLens: cdecode.CodeLens,
    ): Observable<cdecode.CodeLens | undefined> {
        const provider = this.registrations.get<cdecode.CodeLensProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[cdecode.CodeLens | undefined | cdecode.Subscribable<cdecode.CodeLens | undefined | null>]>(document =>
                    [provider.resolveCodeLens(document, codeLens) as
                        (cdecode.CodeLens | undefined | cdecode.Subscribable<cdecode.CodeLens | undefined | null>)]),
            toCodeLength,
        );
    }


    // --- declaration

    public registerDefinitionProvider(selector: cdecode.DocumentSelector, provider: cdecode.DefinitionProvider): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerDefinitionSupport(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public $observeDefinition(
        handle: number,
        resource: UriComponents,
        position: IPosition,
    ): Observable<modes.LocationLink[] | null | undefined> {
        const provider = this.registrations.get<cdecode.DefinitionProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<
                    [cdecode.Definition | cdecode.DefinitionLink[] | null | undefined | cdecode.Subscribable<cdecode.Definition | cdecode.DefinitionLink[] | null | undefined>]
                >(document =>
                    [provider.provideDefinition(document, typeConvert.Position.to(position)) as
                        (cdecode.Definition | cdecode.DefinitionLink[] | null | undefined | cdecode.Subscribable<cdecode.Definition | cdecode.DefinitionLink[] | null | undefined>),
                    ]),
            convertToDefinitionLinks,
        );
    }

    public registerDeclarationSupport(selector: cdecode.DocumentSelector, provider: cdecode.DefinitionProvider): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerDeclarationSupport(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public $observeDeclaration(
        handle: number,
        resource: UriComponents,
        position: IPosition,
    ): Observable<modes.LocationLink[] | null | undefined> {
        const provider = this.registrations.get<cdecode.DeclarationProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<
                    [cdecode.Definition | cdecode.LocationLink[] | null | undefined | cdecode.Subscribable<cdecode.Definition | cdecode.LocationLink[] | null | undefined>]
                >(document => [provider.provideDeclaration(document, typeConvert.Position.to(position)) as
                    cdecode.Definition | cdecode.LocationLink[] | null | undefined | cdecode.Subscribable<cdecode.Definition | cdecode.LocationLink[] | null | undefined>,
                ]),
            convertToDefinitionLinks,
        );
    }

    public registerImplementationProvider(selector: cdecode.DocumentSelector, provider: cdecode.ImplementationProvider): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerImplementationSupport(id, _transformDocumentSelector(selector));
        return subscription;
    }
    public $observeImplementation(handle: number, resource: UriComponents, position: IPosition): Observable<modes.LocationLink[] | null | undefined> {
        const provider = this.registrations.get<cdecode.ImplementationProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<
                    [cdecode.Definition | cdecode.DefinitionLink[] | null | undefined | cdecode.Subscribable<cdecode.Definition | cdecode.DefinitionLink[] | null | undefined>]
                >(document => [provider.provideImplementation(document, typeConvert.Position.to(position)) as
                    (cdecode.Definition | cdecode.DefinitionLink[] | null | undefined | cdecode.Subscribable<cdecode.Definition | cdecode.DefinitionLink[] | null | undefined>),
                ]),
            convertToDefinitionLinks,
        );
    }


    public registerTypeDefinitionProvider(selector: cdecode.DocumentSelector, provider: cdecode.TypeDefinitionProvider): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerTypeDefinitionSupport(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public $observeTypeDefinition(handle: number, resource: UriComponents, position: IPosition): Observable<modes.LocationLink[] | null | undefined> {
        const provider = this.registrations.get<cdecode.TypeDefinitionProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[(cdecode.Definition | cdecode.Subscribable<cdecode.Definition>)]>(document =>
                    [provider.provideTypeDefinition(document, typeConvert.Position.to(position)) as cdecode.Definition | cdecode.Subscribable<cdecode.Definition>],
                ),
            convertToDefinitionLinks,
        );
    }

    // --- extra info

    public registerHoverProvider(selector: cdecode.DocumentSelector, provider: cdecode.HoverProvider, extensionId?: ExtensionIdentifier): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        console.log('[hed834ecd.selector]: ext-language>>', { selector, provider, extensionId });
        this.proxy.$registerHoverProvider(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public $observeHover(handle: number, resource: UriComponents, position: IPosition): Observable<modes.Hover | null | undefined> {
        const provider = this.registrations.get<cdecode.HoverProvider>(handle);
        console.log('[hed834ecd.selector]: observe>>', { handle, resource, position });

        const pos = typeConvert.Position.to(position);
        return toProviderResultObservable<cdecode.Hover, modes.Hover, cdecode.TextDocument, types.Position>(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[(cdecode.Hover | cdecode.Subscribable<cdecode.Hover>), cdecode.TextDocument, types.Position]>(document => {
                    console.log('[check>>>]: hover___', document.text, document);
                    return [provider.provideHover(document, pos) as cdecode.Hover | cdecode.Subscribable<cdecode.Hover>, document, pos];
                }),
            hoverAdapter,
        );
    }


    // --- occurrences

    public registerDocumentHighlightProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentHighlightProvider): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerDocumentHighlightProvider(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public $observeDocumentHighlights(handle: number, resource: UriComponents, position: IPosition): Observable<modes.DocumentHighlight[]> {
        const provider = this.registrations.get<cdecode.DocumentHighlightProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then(document =>
                    [provider.provideDocumentHighlights(document, typeConvert.Position.to(position))] as
                    [cdecode.DocumentHighlight[] | cdecode.Subscribable<cdecode.DocumentHighlight[]>],
                ),
            toDocumentHighlights,
        );
    }

    public registerReferenceProvider(selector: cdecode.DocumentSelector, provider: cdecode.ReferenceProvider): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerReferenceSupport(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public $observeReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext): Observable<modes.Location[] | null | undefined> {
        const provider = this.registrations.get<cdecode.ReferenceProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then(document => {
                    return [provider.provideReferences(document, typeConvert.Position.to(position), context)] as
                        [cdecode.Location[] | cdecode.Subscribable<cdecode.Location[]>];
                }),
            toReferences,
        );
    }

    // --- quick fix


    public registerCodeActionProvider(selector: cdecode.DocumentSelector,
        provider: cdecode.CodeActionProvider, extension?: IExtensionDescription, metadata?: cdecode.CodeActionProviderMetadata): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerQuickFixSupport(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public $observeCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext): Observable<CodeActionDto[] | null | undefined> {
        const provider = this.registrations.get<cdecode.CodeActionProvider>(handle);
        const ran = Selection.isISelection(rangeOrSelection)
            ? <cdecode.Selection>typeConvert.Selection.to(rangeOrSelection as ISelection)
            : <cdecode.Range>typeConvert.Range.to(rangeOrSelection as IRange);
        const allDiagnostics: cdecode.Diagnostic[] = [];

        // for (const diagnostic of this._diagnostics.getDiagnostics(resource)) {
        //     if (ran.intersection(diagnostic.range)) {
        //         allDiagnostics.push(diagnostic);
        //     }
        // }

        const codeActionContext: cdecode.CodeActionContext = {
            diagnostics: allDiagnostics,
            only: context.only ? new CodeActionKind(context.only) : undefined,
        };

        return toProviderResultObservable<(cdecode.Command | cdecode.CodeAction)[], CodeActionDto[], cdecode.CodeActionContext>(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then(
                    document =>
                        [provider.provideCodeActions(document, ran, codeActionContext), codeActionContext] as
                        [(cdecode.Command | cdecode.CodeAction)[] | cdecode.Subscribable<(cdecode.Command | cdecode.CodeAction)[]>, cdecode.CodeActionContext],
                ),
            toCodeActions,
        );
    }

    // --- formatting

    public registerFormattingProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentFormattingEditProvider): Unsubscribable {
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerDocumentFormattingSupport(id, _transformDocumentSelector(selector));
        return subscription;
    }


    public $observeDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions): Observable<ISingleEditOperation[] | null | undefined> {
        const provider = this.registrations.get<cdecode.DocumentFormattingEditProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[(cdecode.TextEdit[] | cdecode.Subscribable<cdecode.TextEdit[]>), cdecode.TextDocument, types.Position]>(document =>
                    [provider.provideDocumentFormattingEdits(document, <any>options) as cdecode.TextEdit[] | cdecode.Subscribable<cdecode.TextEdit[]>,
                        null, null],
                ),
            toTextEdit,
        );
    }


    public $observeDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions): Observable<ISingleEditOperation[] | null | undefined> {
        const provider = this.registrations.get<cdecode.DocumentRangeFormattingEditProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[cdecode.TextEdit[] | null | undefined | cdecode.Subscribable<cdecode.TextEdit[] | null | undefined>]>(document =>
                    [provider.provideDocumentRangeFormattingEdits(document, typeConvert.Range.to(range), <any>options) as
                        cdecode.TextEdit[] | null | undefined | cdecode.Subscribable<cdecode.TextEdit[]>,
                    ],
                ),
            toTextEdit,
        );
    }

    // --- rename

    public registerRenameProvider(selector: cdecode.DocumentSelector, provider: cdecode.RenameProvider, supportsResolveInitialValues: boolean,
    ): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         * register RenameLocationProvider also
         */
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerRenameSupport(id, _transformDocumentSelector(selector), RenameAdapter_supportsResolving(provider));
        return subscription;
    }

    public $observeRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): Observable<WorkspaceEditDto> {
        const provider = this.registrations.get<cdecode.RenameProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then(document =>
                    [provider.provideRenameEdits(document, typeConvert.Position.to(position), newName)] as
                    [cdecode.WorkspaceEdit | null | undefined | cdecode.Subscribable<cdecode.WorkspaceEdit | null | undefined>]
                    ,
                ),
            toWorkspaceEdit,
        );
    }


    public $resolveRenameLocation(handle: number, resource: URI, position: IPosition): Observable<modes.RenameLocation & modes.Rejection> {
        const provider = this.registrations.get<cdecode.RenameProvider>(handle);
        const pos = typeConvert.Position.to(position);
        return toProviderResultObservable<{ range: cdecode.Range, placeholder: string }, modes.RenameLocation & modes.Rejection, cdecode.TextDocument, types.Position>(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then(
                    document => provider.prepareRename(document, pos),
                )
                .catch(err => {
                    let rejectReason = _asMessage(err);
                    if (rejectReason) {
                        return <modes.RenameLocation & modes.Rejection>{ rejectReason, range: undefined, text: undefined };
                    } else {
                        return <any>{ rejectReason: err.message, range: undefined, text: undefined };
                    }
                }),
            RenameAdapter_resolveRenameLocation,
        );
    }

    public $observeSignatureHelp(handle: number, resource: UriComponents, position: IPosition): Observable<modes.SignatureHelp> {
        const provider = this.registrations.get<cdecode.SignatureHelpProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then(document =>
                    [provider.provideSignatureHelp(document, typeConvert.Position.to(position))] as
                    [cdecode.SignatureHelp | null | undefined | cdecode.Subscribable<cdecode.SignatureHelp | null | undefined>],
                ),
            toSignatureHelp,
        );
    }

    public registerSignatureHelpProvider(selector: cdecode.DocumentSelector, provider: cdecode.SignatureHelpProvider, metadata?: string | cdecode.SignatureHelpProviderMetadata | any,
    ): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        const { id, subscription } = this.registrations.add(provider);
        //metadata: ISerializedSignatureHelpProviderMetadata
        this.proxy.$registerSignatureHelpProvider(id, _transformDocumentSelector(selector), metadata);
        return subscription;
    }


    public $observeCompletionItems(handle: number, resource: UriComponents, position: IPosition): Observable<SuggestResultDto> {
        const provider = this.registrations.get<cdecode.CompletionItemProvider>(handle);
        const pos = typeConvert.Position.to(position);
        return toProviderResultObservable<cdecode.CompletionItem[] | cdecode.CompletionList, SuggestResultDto, cdecode.TextDocument, types.Position>(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[(cdecode.CompletionItem[] | cdecode.CompletionList | cdecode.Subscribable<cdecode.CompletionItem[] | cdecode.CompletionList>), cdecode.TextDocument, types.Position]>
                (document => [
                    provider.provideCompletionItems(document, pos) as (cdecode.CompletionItem[] | cdecode.CompletionList | cdecode.Subscribable<cdecode.CompletionItem[] | cdecode.CompletionList>),
                    document, pos],
                ),
            this.suggestAdapter.provideCompletionItems,
        );
    }

    public registerCompletionItemProvider(selector: cdecode.DocumentSelector, provider: cdecode.CompletionItemProvider, triggerCharacters: string[]): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerSuggestSupport(id, _transformDocumentSelector(selector), triggerCharacters, true);
        return subscription;
    }

    public $observeDocumentFormattingEdit(handle: number, resource: UriComponents, options: modes.FormattingOptions): Observable<ISingleEditOperation[]> {
        const provider = this.registrations.get<cdecode.DocumentFormattingEditProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[cdecode.TextEdit[] | null | undefined | cdecode.Subscribable<cdecode.TextEdit[] | null | undefined>]>(document =>
                    [provider.provideDocumentFormattingEdits(document, <any>options) as (cdecode.TextEdit[] | cdecode.Subscribable<cdecode.TextEdit[]>)],
                ),
            toDocumentFormating,
        );
    }


    public $observeDocumentLinks(handle: number, resource: UriComponents): Observable<LinkDto[]> {
        const provider = this.registrations.get<cdecode.DocumentLinkProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[(cdecode.DocumentLink[] | cdecode.Subscribable<cdecode.DocumentLink[]>)]>(document =>
                    [provider.provideDocumentLinks(document) as (cdecode.DocumentLink[] | cdecode.Subscribable<cdecode.DocumentLink[]>)],
                ),
            this.linkAdapter.provideLinks,
        );
    }

    public $resolveDocumentLink(handle: number, link: cdecode.DocumentLink): Observable<modes.ILink> {
        //TODO should been chaged without .getSync(retrieveDocPath(null))
        const provider = this.registrations.get<cdecode.DocumentLinkProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(null))
                .then<[(cdecode.DocumentLink | cdecode.Subscribable<cdecode.DocumentLink>)]>(() =>
                    [provider.resolveDocumentLink(link) as (cdecode.DocumentLink | cdecode.Subscribable<cdecode.DocumentLink>)],
                ),
            this.linkAdapter.resolveLink,
        );
    }

    public registerDocumentLinkProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentLinkProvider): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerDocumentLinkProvider(id, _transformDocumentSelector(selector));
        console.trace('[check>>>]: registerDocumentLinkProvider', { selector, provider });
        return subscription;
    }

    public $observeDocumentColors(handle: number, resource: UriComponents): Observable<IRawColorInfo[]> {
        const provider = this.registrations.get<cdecode.DocumentColorProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[(cdecode.ColorInformation[] | cdecode.Subscribable<modes.IColorInformation[]>)]>(document =>
                    [provider.provideDocumentColors(document) as (cdecode.ColorInformation[] | cdecode.Subscribable<modes.IColorInformation[]>)],
                ),
            provideColors,
        );
    }

    public $observeColorPresentations(handle: number, resource: UriComponents, raw: IRawColorInfo): Observable<modes.IColorPresentation[]> {
        //TODO should been chaged without .getSync(retrieveDocPath(null))
        const provider = this.registrations.get<cdecode.DocumentColorProvider>(handle);
        const range = typeConvert.Range.to(raw.range);
        const color = typeConvert.Color.to(raw.color);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[(cdecode.ColorPresentation[] | cdecode.Subscribable<cdecode.ColorPresentation[]>)]>(document =>
                    [provider.provideColorPresentations(color, { document, range }) as (cdecode.ColorPresentation[] | cdecode.Subscribable<cdecode.ColorPresentation[]>)],
                ),
            provideColorPresentations,
        );
    }

    public registerColorProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentColorProvider): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerDocumentColorProvider(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public registerDocumentRangeFormattingEditProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentRangeFormattingEditProvider): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerRangeFormattingSupport(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public registerOnTypeFormattingEditProvider(selector: cdecode.DocumentSelector, provider: cdecode.OnTypeFormattingEditProvider, autoFormatTriggerCharacters: string[]): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerOnTypeFormattingSupport(id, _transformDocumentSelector(selector), autoFormatTriggerCharacters);
        return subscription;
    }

    public registerFoldingRangeProvider(selector: cdecode.DocumentSelector, provider: cdecode.FoldingRangeProvider): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        const { id, subscription } = this.registrations.add(provider);
        this.proxy.$registerFoldingRangeProvider(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public $observeFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext): Observable<modes.FoldingRange[]> {
        //TODO should been chaged without .getSync(retrieveDocPath(null))
        const provider = this.registrations.get<cdecode.FoldingRangeProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(retrieveDocPath(resource))
                .then<[(cdecode.FoldingRange[] | cdecode.Subscribable<cdecode.FoldingRange[]>)]>(document =>
                    [provider.provideFoldingRanges(document, context) as (cdecode.FoldingRange[] | cdecode.Subscribable<cdecode.FoldingRange[]>)],
                ),
            provideFoldingRanges,
        );
    }

    //==============

    public registerWorkspaceSymbolProvider(provider: cdecode.WorkspaceSymbolProvider): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        const { id, subscription } = this.registrations.add(provider);
        // this.proxy.(id, _transformDocumentSelector(selector));
        return subscription;
    }

    public $observeWorkspaceSymbols(handle: number, search: string): Observable<WorkspaceSymbolsDto> {
        const provider = this.registrations.get<cdecode.WorkspaceSymbolProvider>(handle);
        //TODO delete getSync
        return toProviderResultObservable(
            this.documents
                .getSync(null)
                .then<[(cdecode.SymbolInformation[] | cdecode.Subscribable<cdecode.SymbolInformation[]>)]>(() =>
                    [provider.provideWorkspaceSymbols(search) as (cdecode.SymbolInformation[] | cdecode.Subscribable<cdecode.SymbolInformation[]>)],
                ),
            provideWorkspaceSymbols,
        );
    }

    public $resolveWorkspaceSymbol(handle: number, symbol: cdecode.SymbolInformation): Observable<WorkspaceSymbolDto> {
        const provider = this.registrations.get<cdecode.WorkspaceSymbolProvider>(handle);
        return toProviderResultObservable(
            this.documents
                .getSync(null)
                .then<[(cdecode.SymbolInformation | cdecode.Subscribable<cdecode.SymbolInformation>), cdecode.SymbolInformation, types.Position]>(document =>
                    [provider.resolveWorkspaceSymbol(symbol) as (cdecode.SymbolInformation | cdecode.Subscribable<cdecode.SymbolInformation>), symbol, null],
                ),
            resolveWorkspaceSymbol,
        );
    }
    //==============================

    public setLanguageConfiguration(language: string, configuration: cdecode.LanguageConfiguration): Unsubscribable {
        /**
         * {@link idStr} is the `id` parameter to {@link sourcegraph.languages.registerLocationProvider} that
         * identifies the provider and is chosen by the extension. {@link id} is an internal implementation detail:
         * the numeric registry ID used to identify this provider solely between the client and extension host.
         */
        let { wordPattern } = configuration;

        // check for a valid word pattern
        if (wordPattern && regExpLeadsToEndlessLoop(wordPattern)) {
            throw new Error(`Invalid language configuration: wordPattern '${wordPattern}' is not allowed to match the empty string.`);
        }

        const serializedConfiguration: ISerializedLanguageConfiguration = {
            comments: configuration.comments,
            brackets: configuration.brackets,
            __electricCharacterSupport: configuration.__electricCharacterSupport,
            __characterPairSupport: configuration.__characterPairSupport,
        };


        const { id, subscription } = this.registrations.add(language);
        this.proxy.$setLanguageConfiguration(id, language, serializedConfiguration);
        return subscription;
    }

    public unsubscribe(): void {
        this.registrations.unsubscribe();
    }
}

// function toLocations(result: Location[] | null | undefined): clientType.ILocation[] | null | undefined {
//     return result ? result.map(location => fromLocation(location)) : result as any;
// }



function retrieveDocPath(resource: UriComponents) {
    if (!resource) {
        return undefined;
    }
    return URI.revive(resource).toString();
}


