import { from, Observable, Subscription } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import * as cdecode from 'cdecode/client';
import { WebExtHostLanguageFeaturesShape, ExtHostLanguageFeaturesShape, IDocumentRangeFormattingEditsParams } from '@workbench-stack/extension-api';
import { ClientThreadLanguageFeaturesShape } from '@workbench-stack/extension-api/lib/protocol/client.protocol';
import { ExtHostContext } from '@workbench-stack/extension-api/lib/protocol/server.protocol';
import {
    ISerializedDocumentFilter, SuggestResultDto,
    WorkspaceEditDto,
} from '@workbench-stack/extension-api/lib/protocol/common.protocol';
import { SubscriptionMap } from '../common';
import {
    createProxy,
    createProxyAndHandleRequests, Connection, FeatureProviderRegistry,
    TextDocumentReferencesProviderRegistry, TextDocumentLocationProviderIDRegistry,
    ProvideHoverSignature, ProvideTextDocumentLocationSignature,
    TextDocumentСompletionProviderRegistry, ProvideDocumentFormattingEditSignature,
    ProvideDocumentHighlightSignature, IRenameParams, ICompletionParams,
    ProvideCompletionItemSignature, IReferencesParams, IDocumentFormattingEditsParams,
    TextDocumentHightLightProviderRegistry, TextDocumentRenameProviderRegistry,
    TextDocumentFormattingProviderRegistry, TextDocumentSymbolDefinitionProviderRegistry,
    ProvideRenameSignature,
    ITextDocumentLinkParams, ITextDocumentColorInfoParams, WorkspaceSymbolsDto,
    ProvideDocumentSymbolSignature, ITextDocumentRegistrationOptions, ICodeLensParams,
} from '@workbench-stack/extension-api';
import { logger } from '@cdm-logger/client';
import * as modes from '@vscode/monaco-editor/esm/vs/editor/common/modes';
import { _reviveLocationLinkDto, _reviveLocationDto, toCompletionSuggestion } from './client-language-feature-helper';
import { ITextModel, ISingleEditOperation } from '@vscode/monaco-editor/esm/vs/editor/common/model';
import { ExtHostContext as ServerExtHostContext } from '@workbench-stack/extension-api/lib/protocol/server.protocol';
import { ExtensionIdentifier, ITextDocumentPositionParams } from '@workbench-stack/core';
import { UriComponents } from '@vscode/monaco-editor/esm/vs/base/common/uri';


type DocumentSelector = Pick<cdecode.DocumentSelector, Exclude<keyof cdecode.DocumentFilter, cdecode.GlobPattern>> & {
    pattern: string;
};
export class ClientLanguageFeatures /*implements ClientThreadLanguageFeaturesShape*/ {
    private _server?: any;
    private subscriptions = new Subscription();
    private registrations = new SubscriptionMap();
    private proxy: WebExtHostLanguageFeaturesShape;
    private serverProxy: ExtHostLanguageFeaturesShape;

    constructor(
        connection: Connection,
        private hoverRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            ProvideHoverSignature
        >,
        private definitionRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            ProvideTextDocumentLocationSignature
        >,
        private typeDefinitionRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            ProvideTextDocumentLocationSignature
        >,
        private implementationRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            ProvideTextDocumentLocationSignature
        >,
        private referencesRegistry: TextDocumentReferencesProviderRegistry,
        private completionRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            ProvideCompletionItemSignature
        >,
        private hightLightRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            ProvideDocumentHighlightSignature
        >,
        private renameRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions, ProvideRenameSignature
        >,
        private formattingRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            ProvideDocumentFormattingEditSignature
        >,
        private referenceRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            any
        >,
        private outlineSupportRegistry: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            any
        >,
        private registerCodeActionsProvider: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            any
        >,
        private registerCodeLensProvider: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            any
        >,
        private registerLinkProvider: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            any
        >,
        private registerColorProvider: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            any
        >,
        private registerFoldingRangeProvider: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            any
        >,
        private registerWorkspaceSymbol: FeatureProviderRegistry<
            ITextDocumentRegistrationOptions,
            any
        >,
        private rangeFormattingEditProviderRegistry: any,
    ) {
        this.subscriptions.add(this.registrations);

        this.proxy = createProxyAndHandleRequests('languageFeatures', connection, this);
        this.serverProxy = createProxyAndHandleRequests(ServerExtHostContext.ExtHostLanguageFeatures.sid, connection, this);
    }

    public $unregister(id: number): void {
        this.registrations.remove(id);
    }

    //#endregion

    // --- declaration

    public $registerDefinitionSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.definitionRegistry.registerProvider(
                { documentSelector: selector },
                (params: ITextDocumentPositionParams): Observable<modes.LocationLink[] | null | undefined> =>
                    from(this.proxy.$observeDefinition(id, params.textDocument.uri as UriComponents, params.position)
                        .pipe(
                            map(data => <modes.LocationLink[]>_reviveLocationLinkDto(data)),
                        ),
                    ),
            ),
        );
    }

    public $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier, displayName: string) {
        console.log('[check>>>]: $registerRangeFormattingSupport.main >>', { handle, selector, extensionId, displayName });
        this.registrations.add(
            handle,
            this.rangeFormattingEditProviderRegistry.register(
                { documentSelector: selector },
                (model: ITextModel, range: any, options: modes.FormattingOptions) =>
                    from(this.proxy.$observeDocumentRangeFormattingEdits(handle, model.uri, range, options)),
            ),
        );
    }

    /// exist in new version
    // public $registerDeclarationSupport(id: number, selector: ISerializedDocumentFilter[]): void {
    //     this.registrations.add(
    //         id,
    //         this.definitionRegistry.registerProvider(
    //             { documentSelector: selector },
    //             (params: ITextDocumentPositionParams): Observable<modes.Declaration | modes.LocationLink[]> =>
    //                 from(this.proxy.$observeDeclaration(id, params.textDocument.uri, params.position)).pipe(
    //                     map(result => _reviveLocationLinkDto(result)),
    //                 ),
    //         ),
    //     );
    // }

    public $registerImplementationSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.implementationRegistry.registerProvider(
                { documentSelector: selector },
                (params: ITextDocumentPositionParams): Observable<modes.Location[]> =>
                    from(this.proxy.$observeImplementation(id, params.textDocument.uri as UriComponents, params.position)).pipe(
                        map(result => <modes.Location[]>_reviveLocationLinkDto(result),
                        ),
                    ),
            ),
        );
    }

    public $registerTypeDefinitionSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.typeDefinitionRegistry.registerProvider(
                { documentSelector: selector },
                (params: ITextDocumentPositionParams): Observable<modes.Location[]> =>
                    from(this.proxy.$observeTypeDefinition(id, params.textDocument.uri as UriComponents, params.position)).pipe(
                        map(result => <modes.Location[]>_reviveLocationLinkDto(result),
                        ),
                    ),
            ),
        );
    }

    public $registerOutlineSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.outlineSupportRegistry.registerProvider(
                { documentSelector: selector },
                (params): Observable<any> => {
                    const data = this.proxy.$observeDocumentSymbols(id, params.textDocument.uri);
                    return from(data).pipe(
                        map(result => {
                            return result;
                        }),
                    );
                },
            ),
        );
    }

    public $registerQuickFixSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.registerCodeActionsProvider.registerProvider(
                { documentSelector: selector },
                (params): Observable<any> => {
                    const data = this.proxy.$observeCodeActions(id, params.textDocument.uri, params.rangeOrSelection, params.context);
                    return from(data).pipe(
                        map(result => {
                            return result;
                        }),
                    );
                },
            ),
        );
    }

    public $registerReferenceSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.referenceRegistry.registerProvider(
                { documentSelector: selector },
                (params): Observable<any> => {
                    const data = this.proxy.$observeReferences(id, params.textDocument.uri, params.position, params.context);
                    return from(data).pipe(
                        map(result => {
                            return result;
                        }),
                    );
                },
            ),
        );
    }


    // --- extra info


    public $registerHoverProvider(id: number, selector: ISerializedDocumentFilter[]): void {
        console.log('[hed834ecd.selector]: client-language>>', { id, selector });
        this.registrations.add(
            id,
            this.hoverRegistry.registerProvider(
                { documentSelector: selector },
                (params: ITextDocumentPositionParams): Observable<modes.Hover> => {
                    console.log('[hed834ecd.selector]: fn>>>', { params });
                    let data: Observable<modes.Hover> | Promise<modes.Hover>;
                    if (id < 0) {
                        data = this.serverProxy.$provideHover(id, params.textDocument.uri as UriComponents, params.position);
                    } else {
                        data = this.proxy.$observeHover(id, params.textDocument.uri as UriComponents, params.position);
                    }

                    return from(data).pipe(
                        map(result => {
                            return result;
                        }),
                    );
                },
            ),
        );
    }

    // --- occurrences

    public $registerDocumentHighlightProvider(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.hightLightRegistry.registerProvider(
                { documentSelector: selector },
                (params: ITextDocumentPositionParams): Observable<modes.DocumentHighlight[]> =>
                    from(this.proxy.$observeDocumentHighlights(id, params.textDocument.uri as UriComponents, params.position)).pipe(
                        map(result => result || []),
                    ),
            ),
        );
    }

    // --- references

    public $registerReferenceProvider(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.referencesRegistry.registerProvider(
                { documentSelector: selector },
                (params: IReferencesParams): Observable<modes.Location[]> => {
                    const data = this.proxy.$observeReferences(id, params.textDocument.uri as UriComponents, params.position, params.context);
                    return from(data).pipe(
                        map(result => <modes.Location[]>_reviveLocationDto(result)),
                    );
                },
            ),
        );
    }


    public $registerSuggestSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.completionRegistry.registerProvider(
                { documentSelector: selector },
                (params: ICompletionParams): Observable<SuggestResultDto> =>
                    from(this.proxy.$observeCompletionItems(id, params.textDocument.uri as UriComponents, params.position, params.context))
                        .pipe(
                            map(toCompletionSuggestion),
                        ),
            ),
        );
    }


    // -- rename
    public $registerRenameSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.renameRegistry.registerProviders([{
                registrationOptions: { documentSelector: selector },
                provider: (params: IRenameParams): Observable<WorkspaceEditDto> =>
                    from(this.proxy.$observeRenameEdits(id, params.textDocument.uri as UriComponents, params.position, params.rename))
                    .pipe(
                        map(result => result),
                    ),
            }, {
                registrationOptions: { documentSelector: selector },
                provider: (params: IRenameParams): Observable<modes.RenameLocation> =>
                    from(this.proxy.$resolveRenameLocation(id, params.textDocument.uri as UriComponents, params.position))
                    .pipe(
                        map(result => result),
                    ),
            }]),
        );
    }

    public $registerDocumentFormattingSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.formattingRegistry.registerProvider(
                { documentSelector: selector },
                (params: IDocumentFormattingEditsParams): Observable<ISingleEditOperation[]> =>
                    from(this.proxy.$observeDocumentFormattingEdits(id, params.textDocument.uri as UriComponents, params.options))
                        .pipe(
                            map(result => result as any),
                        ),
            ),
        );
    }

    public $registerCodeLensProvider(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.registerCodeLensProvider.registerProviders([{
                registrationOptions: { documentSelector: selector },
                provider: (params: IDocumentFormattingEditsParams): Observable<modes.ICodeLensSymbol> =>
                    from(this.proxy.$observeCodeLenses(id, params.textDocument.uri as UriComponents))
                        .pipe(
                            map(result => result as any),
                        ),
            }, {
                registrationOptions: { documentSelector: selector },
                provider: (params: ICodeLensParams): Observable<modes.ICodeLensSymbol> =>
                    from(this.proxy.$resolveCodeLens(id, params.textDocument.uri as UriComponents, params.symbol))
                        .pipe(
                            map(result => result as any),
                        ),
            }]),
        );
    }

    public $registerCodeLensSupport(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.registerCodeLensProvider.registerProviders([{
                registrationOptions: { documentSelector: selector },
                provider: (params: IDocumentFormattingEditsParams): Observable<modes.ICodeLensSymbol[]> => {
                    if (id < 0) {
                        return from(this.serverProxy.$provideCodeLenses(id, params.textDocument.uri, null));
                    } else {
                        return this.proxy.$observeCodeLenses(id, params.textDocument.uri);
                    }
                },
            }, {
                registrationOptions: { documentSelector: selector },
                provider: (params: ICodeLensParams): Observable<modes.ICodeLensSymbol[]> => {
                    if (id < 0) {
                        return from(this.serverProxy.$provideCodeLenses(id, params.textDocument.uri, null));
                    } else {
                        return this.proxy.$observeCodeLenses(id, params.textDocument.uri);
                    }
                },
            }]),
        );
    }

    public $registerLinkProvider(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.registerLinkProvider.registerProviders([{
                registrationOptions: { documentSelector: selector },
                provider: (params: IDocumentFormattingEditsParams): Observable<modes.ILink[]> =>
                    from(this.proxy.$observeDocumentLinks(id, params.textDocument.uri as UriComponents))
                        .pipe(
                            map(result => result as any),
                        ),
            }, {
                registrationOptions: { documentSelector: selector },
                provider: (params: ITextDocumentLinkParams): Observable<modes.ILink> =>
                    from(this.proxy.$resolveDocumentLink(id, params.link))
                        .pipe(
                            map(result => result as any),
                        ),
            }]),
        );
    }

    public $registerColorProvider(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.registerColorProvider.registerProviders([{
                registrationOptions: { documentSelector: selector },
                provider: (params: IDocumentFormattingEditsParams): Observable<modes.ILink[]> =>
                    from(this.proxy.$observeDocumentColors(id, params.textDocument.uri as UriComponents))
                        .pipe(
                            map(result => result as any),
                        ),
            }, {
                registrationOptions: { documentSelector: selector },
                provider: (params: ITextDocumentColorInfoParams): Observable<modes.ILink> =>
                    from(this.proxy.$observeColorPresentations(id, params.textDocument.uri as UriComponents, params.colorInfo))
                        .pipe(
                            map(result => result as any),
                        ),
            }]),
        );
    }

    public $registerFoldingRangeProvider(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.registerFoldingRangeProvider.registerProvider(
                { documentSelector: selector },
                (params: any): Observable<modes.FoldingRange[]> => {
                    const data = this.proxy.$observeFoldingRanges(id, params.textDocument.uri, params.context);
                    return from(data).pipe(
                        map(result => result as any),
                    );
                },
            ),
        );
    }

    public $registerWorkspaceSymbolProvider(id: number, selector: ISerializedDocumentFilter[]): void {
        this.registrations.add(
            id,
            this.registerWorkspaceSymbol.registerProviders([{
                registrationOptions: { documentSelector: selector },
                provider: (params: any): Observable<WorkspaceSymbolsDto> => {
                    const data = this.proxy.$observeWorkspaceSymbols(id, params.search);
                    return from(data).pipe(
                        map(result => result as any),
                    );
                },
            }, {
                registrationOptions: { documentSelector: selector },
                provider: (params: any): Observable<WorkspaceSymbolsDto> => {
                    const data = this.proxy.$resolveWorkspaceSymbol(id, params.WorkspaceSymbolDto);
                    return from(data).pipe(
                        map(result => result as any),
                    );
                },
            }]),
        );
    }

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