import { Observable, of, asyncScheduler } from 'rxjs';
import { take, switchMap, observeOn } from 'rxjs/operators';
import * as cdecode from 'cdecode/client';
import { Range, WorkspaceEdit } from '@workbench-stack/extension-api/lib/core/types/ext-host-types';
import { languages as cdecodeLanguages } from 'cdecode/client';
import { IServices, UriState } from '@workbench-stack/extension-api';
import { createBarrier, integrationTestContext } from './test-helper';
import { URI } from '@vscode/monaco-editor/esm/vs/base/common/uri';
import 'jest';


export function assertToJSON(a: any, expected: any): void {
    const raw = JSON.stringify(a);
    const actual = JSON.parse(raw);
    console.log('actual=========>', actual);
    expect(actual).toEqual(expected);
}

xdescribe('LanguageFeatures registerHoverProvider', () => {

    testLocationProvider<cdecode.HoverProvider>(
        'registerHoverProvider',
        extensionHost => extensionHost.languages.registerHoverProvider,
        label => ({
            provideHover: (doc: cdecode.TextDocument, pos: cdecode.Position) => {
                return {
                    contents: [label],
                };
            },
        }),
        labels => ({
            contents: labels.map(label => ({ value: label })),
            range: { endColumn: 2, endLineNumber: 1, startColumn: 2, startLineNumber: 1 },
        }),
        run => ({ provideHover: run } as cdecode.HoverProvider),
        services =>
            services.textDocumentHover.getHover({
                textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                position: { lineNumber: 1, column: 2 },
            }),
    );

});

xdescribe('LanguageFeatures (DefinitionProvider) || all passed', () => {

        testLocationProvider<cdecode.DefinitionProvider>(
            'registerDefinitionProvider',
            extensionHost => extensionHost.languages.registerDefinitionProvider,
            label => ({
                provideDefinition: (doc: cdecode.TextDocument, pos: cdecode.Position) => {
                    return [{
                        uri: URI.from({ path: `file:///${label}`, scheme: 'file' }),
                        range: undefined,
                    }] as cdecode.Location[];
                },

                // of([{ uri: URI.from({path: `file:///${label}`, scheme: 'file'}) }]).pipe(observeOn(asyncScheduler)),
            }),
            labeledDefinitionResults,
            run => ({ provideDefinition: run } as cdecode.DefinitionProvider),
            services =>
                services.textDocumentDefinition
                    .getLocations({
                        textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                        position: { lineNumber: 1, column: 2 },
                    }),
        );
});

xdescribe('LanguageFeatures (ReferenceProvider) || all passed', () => {

        testLocationProvider<cdecode.ReferenceProvider>(
            'registerReferenceProvider',
            extensionHost => extensionHost.languages.registerReferenceProvider,
            label => ({
                provideReferences: (
                    doc: cdecode.TextDocument,
                    pos: cdecode.Position,
                    context: cdecode.ReferenceContext,
                ) => [{
                    uri: URI.from({ path: `file:///${label}`, scheme: 'file' }),
                    range: undefined,
                }] as any, //like it should be
                //to make it work I need create array of array[]
                // of([{ uri: new URL(`file:///${label}`) }]).pipe(observeOn(asyncScheduler)),
            }),
            labels => labels.map(label => ({ uri: URI.from({ path: `file:///${label}`, scheme: 'file' }), range: undefined })),
            run =>
                ({
                    provideReferences: (
                        doc: cdecode.TextDocument,
                        pos: cdecode.Position,
                        _context: cdecode.ReferenceContext,
                    ) => run(doc, pos),
                } as cdecode.ReferenceProvider),
            services =>
                services.textDocumentReferences
                    .getLocations({
                        textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                        position: { lineNumber: 1, column: 2 },
                        context: { includeDeclaration: true },
                    }),
        );

});

xdescribe('LanguageFeatures (DocumentSymbolProvider) not work', () => {

        testLocationProviderDoc<cdecode.DocumentSymbolProvider>(
            'registerDefinitionProvider',
            extensionHost => extensionHost.languages.registerDocumentSymbolProvider,
            label => ({
                provideDocumentSymbols: (doc: cdecode.TextDocument) => {
                    return [{
                        name: 'test',
                        detail: 'test',
                        kind: 1,
                        range: new Range(1, 1, 1, 1),
                        selectionRange: new Range(1, 1, 1, 1),
                        children: [],
                    }] as cdecode.DocumentSymbol[];
                },

                // of([{ uri: URI.from({path: `file:///${label}`, scheme: 'file'}) }]).pipe(observeOn(asyncScheduler)),
            }),
            documentSymbolsResults,
            run => ({ provideDocumentSymbols: run } as cdecode.DocumentSymbolProvider),
            services =>
                services.textDocumentDefinition
                    .getLocations({
                        textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                        position: { lineNumber: 1, column: 2 },
                    }),
        );

});

xdescribe('LanguageFeatures (DocumentHighlightProvider) || all passed', () => {
        testLocationProvider<cdecode.DocumentHighlightProvider>(
            'registerDocumentHighlightProvider',
            extensionHost => extensionHost.languages.registerDocumentHighlightProvider,
            label => ({
                provideDocumentHighlights: (doc: cdecode.TextDocument, pos: cdecode.Position) => {
                    return [{
                        range: undefined,
                    }] as cdecode.DocumentHighlight[];
                },

                // of([{ uri: URI.from({path: `file:///${label}`, scheme: 'file'}) }]).pipe(observeOn(asyncScheduler)),
            }),
            documentHighLight,
            run => ({ provideDocumentHighlights: run } as cdecode.DocumentHighlightProvider),
            services =>
                services.textDocumentHightLight
                    .getHightLight({
                        textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                        position: { lineNumber: 1, column: 2 },
                    }),
        );
});

xdescribe('LanguageFeatures (RenameProvider) unregister not work', () => {
    testLocationProvider<cdecode.RenameProvider>(
        'registerRenameProvider',
        extensionHost => extensionHost.languages.registerRenameProvider,
        label => ({
            provideRenameEdits: (
                doc: cdecode.TextDocument,
                pos: cdecode.Position,
                newName: string,
            ) => {
                return new WorkspaceEdit();
            },
        }),
        renaneProvider,
        run =>
            ({
                provideRenameEdits: (
                    doc: cdecode.TextDocument,
                    pos: cdecode.Position,
                    newName: string,
                ) => run(doc, pos),
            } as cdecode.RenameProvider),
        services =>
            services.textDocumentRename
                .getRename({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                    context: { includeDeclaration: true },
                }),
    );
});

xdescribe('LanguageFeatures (CodeActionProvider) || all passed', () => {

    testLocationProvider<cdecode.CodeActionProvider>(
        'registerCodeActionsProvider',
        extensionHost => extensionHost.languages.registerCodeActionsProvider,
        label => ({
            provideCodeActions: (
                doc: cdecode.TextDocument,
                pos: cdecode.Range | cdecode.Selection,
                context: cdecode.CodeActionContext,
            ) => [{
                id: 'test',
                title: 'test',
            }],
            // of([{ uri: new URL(`file:///${label}`) }]).pipe(observeOn(asyncScheduler)),
        }),
        сodeActions,
        run =>
            ({
                provideCodeActions: (
                    doc: cdecode.TextDocument,
                    pos: cdecode.Range | cdecode.Selection,
                    _context: cdecode.CodeActionContext,
                ) => run(doc, pos),
            } as cdecode.CodeActionProvider),
        services =>
            services.textDocumentCodeAction
                .getCodeActions({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                    context: { includeDeclaration: true },
                }),
    );

});

xdescribe('LanguageFeatures (DocumentFormattingEditProvider) || all passed --------', () => {

    testLocationProvider<cdecode.DocumentFormattingEditProvider>(
        'registerDocumentFormattingEditProvider',
        extensionHost => extensionHost.languages.registerDocumentFormattingEditProvider,
        label => ({
            provideDocumentFormattingEdits: (doc: cdecode.TextDocument, options: cdecode.FormattingOptions) => {
                return [{
                    range: undefined,
                    newText: 'some',
                }] as cdecode.TextEdit[];
            },

            // of([{ uri: URI.from({path: `file:///${label}`, scheme: 'file'}) }]).pipe(observeOn(asyncScheduler)),
        }),
        formattingEditProvider,
        run => ({ provideDocumentFormattingEdits: run } as cdecode.DocumentFormattingEditProvider),
        services =>
            services.textDocumentFormatting
                .getFormatting({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    options: { tabSize: 2, insertSpaces: true},
                }),
    );

});

xdescribe('LanguageFeatures (ImplementationProvider) || all passed', () => {

    testLocationProvider<cdecode.ImplementationProvider>(
        'registerImplementationProvider',
        extensionHost => extensionHost.languages.registerImplementationProvider,
        label => ({
            provideImplementation: (doc: cdecode.TextDocument, position: cdecode.Position) => {
                return [{
                    uri: URI.from({ path: `file:///${label}`, scheme: 'file' }),
                    range: undefined,
                }] as cdecode.Location[];
            },
        }),
        labeledDefinitionResults,
        run => ({ provideImplementation: run } as cdecode.ImplementationProvider),
        services =>
            services.textDocumentImplementation
                .getLocations({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                }),
    );
});

xdescribe('LanguageFeatures (TypeDefinitionProvider) || all passed', () => {

    testLocationProvider<cdecode.TypeDefinitionProvider>(
        'registerTypeDefinitionProvider',
        extensionHost => extensionHost.languages.registerTypeDefinitionProvider,
        label => ({
            provideTypeDefinition: (doc: cdecode.TextDocument, position: cdecode.Position) => {
                return [{
                    uri: URI.from({ path: `file:///${label}`, scheme: 'file' }),
                    range: undefined,
                }] as cdecode.Location[];
            },
        }),
        labeledDefinitionResults,
        run => ({ provideTypeDefinition: run } as cdecode.TypeDefinitionProvider),
        services =>
            services.textDocumentTypeDefinition
                .getLocations({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                }),
    );

});

xdescribe('LanguageFeatures (CompletionItemProvider) unregister not work', () => {

    testLocationProvider<cdecode.CompletionItemProvider>(
        'registerCompletionItemProvider',
        extensionHost => extensionHost.languages.registerCompletionItemProvider,
        label => ({
            provideCompletionItems: (doc: cdecode.TextDocument, position: cdecode.Position) => {
                return [{
                    label: 'test',
                    insertText: 'test',
                    range: { start: {line: 0, character: 0}, end: {line: 0, character: 0}},
                    command: {
                        title: 'testTitle',
                        command: 'command',
                    },
                }] as cdecode.CompletionItem[];
            },
        }),
        completionItemProvider,
        run => ({ provideCompletionItems: run } as cdecode.CompletionItemProvider),
        services =>
            services.textDocumentСompletion
                .getCompletion({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                    context: {
                        triggerKind: 1,
                    },
                }),
    );

});

xdescribe('LanguageFeatures (CodeInsertRegistry )', () => {

    testLocationProvider<cdecode.CompletionItemProvider>(
        'registerCompletionItemProvider',
        extensionHost => extensionHost.languages.registerCompletionItemProvider,
        label => ({
            provideCompletionItems: (doc: cdecode.TextDocument, position: cdecode.Position) => {
                return [{
                    label: 'test',
                    insertText: 'test',
                    range: undefined,
                }] as cdecode.CompletionItem[];
            },
        }),
        labeledDefinitionResults,
        run => ({ provideCompletionItems: run } as cdecode.CompletionItemProvider),
        services =>
            services.textDocumentСompletion
                .getCompletion({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                    context: {
                        triggerKind: 1,
                    },
                }),
    );

});

xdescribe('LanguageFeatures (CodeInsertRegistry ) unregister not work', () => {

    testLocationProvider<cdecode.FoldingRangeProvider>(
        'registerFoldingRangeProvider',
        extensionHost => extensionHost.languages.registerFoldingRangeProvider,
        label => ({
            provideFoldingRanges: (doc: cdecode.TextDocument, position: cdecode.Position) => {
                return [{
                    start: 0,
                    end: 0,
                }];
            },
        }),
        foldingRangeProvider,
        run => ({ provideFoldingRanges: run } as cdecode.FoldingRangeProvider),
        services =>
            services.textDocumentFoldingRanges
                .getFoldingRange({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                    context: {
                        triggerKind: 1,
                    },
                }),
    );

});

xdescribe('LanguageFeatures (CodeInsertRegistry ) unregister not work', () => {

    testLocationProvider<cdecode.WorkspaceSymbolProvider>(
        'registerWorkspaceSymbolProvider',
        extensionHost => extensionHost.languages.registerWorkspaceSymbolProvider,
        label => ({
            provideWorkspaceSymbols: (doc: cdecode.TextDocument, position: cdecode.Position) => {
                return [{
                    start: 0,
                    end: 0,
                }];
            },
        }),
        provideWorkspaceSymbols,
        run => ({ provideWorkspaceSymbols: run } as cdecode.IWorkspaceSymbol[]),
        services =>
            services.textDocumentWorkspaceSymbol
                .getWorkspaceSymbol({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                    context: {
                        triggerKind: 1,
                    },
                }),
    );

});

xdescribe('LanguageFeatures (FoldingRange ) ', () => {

    testLocationProvider<cdecode.FoldingRange[]>(
        'registerWorkspaceSymbolProvider',
        extensionHost => extensionHost.languages.registerFoldingRangeProvider,
        label => ({
            provideFoldingRanges: (doc: cdecode.TextDocument, position: cdecode.Position) => {
                return [{
                    start: 0,
                    end: 0,
                }];
            },
        }),
        workspaceSymbolProvider,
        run => ({ provideFoldingRanges: run } as cdecode.FoldingRange[]),
        services =>
            services.textDocumentFoldingRanges
                .getFoldingRange({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                    context: {
                        triggerKind: 1,
                    },
                }),
    );

});

xdescribe('LanguageFeatures (WorkspaceSymbolProvider ) not implemented on server', () => {

    testLocationProvider<cdecode.WorkspaceSymbolProvider>(
        'registerWorkspaceSymbolProvider',
        extensionHost => extensionHost.languages.registerWorkspaceSymbolProvider,
        label => ({
            resolveWorkspaceSymbol: (doc: cdecode.TextDocument, position: cdecode.Position) => {
                return {
                    name: 'test',
                    containerName: 'test',
                    kind: 1,
                    location: {
                        uri: 'test',
                        range: {
                            start: 0,
                            end: 0,
                        },
                    },
                };
            },
        }),
        workspaceSymbolProvider,
        run => ({ resolveWorkspaceSymbol: run } as cdecode.SymbolInformation),
        services =>
            services.textDocumentWorkspaceSymbol
                .getWorkspaceSymbol({
                    textDocument: { uri: <UriState>URI.parse('file:///f').toJSON() },
                    position: { lineNumber: 1, column: 2 },
                    context: {
                        triggerKind: 1,
                    },
                }),
    );

});
/**
 * Generates test cases for cdecode.languages.registerXyzProvider functions and their associated
 * XyzProviders, for providers that return a list of locations.
 */
function testLocationProvider<P>(
    name: keyof typeof cdecodeLanguages,
    registerProvider: (
        extensionHost: typeof cdecode,
    ) => (selector: cdecode.DocumentSelector, provider: P) => cdecode.Unsubscribable,
    labeledProvider: (label: string) => P,
    labeledProviderResults: (lables: string[]) => any,
    providerWithImpl: (run: (doc: cdecode.TextDocument, pos: cdecode.Position | cdecode.Range | cdecode.Selection | cdecode.FormattingOptions) => void) => P,
    getResult: (services: IServices) => Observable<any>,
): void {
    jest.setTimeout(50000);
       describe(`languages.${name}`, () => {
        test('registers and unregisters a single provider', async () => {
            const { services, extensionHost } = await integrationTestContext();

            // Register the provider and call it.
            const unsubscribe = registerProvider(extensionHost)(['*'], labeledProvider('a'));
            await extensionHost.internal.sync();
            const res = await getResult(services)
                .pipe(
                    take(1),
                ).toPromise();
            console.log('res=====>', res);
            expect(
                res,
            ).toEqual(labeledProviderResults(['a']));

            // Unregister the provider and ensure it's removed.
            console.log('unsubscribe------------->', unsubscribe);
            // unsubscribe.unsubscribe();

            // expect(
            //     await getResult(services)
            //         .pipe(take(1))
            //         .toPromise(),
            // ).toEqual(null);
        });

        // test('supplies params to the provideXyz method', async () => {
        //     const { services, extensionHost } = await integrationTestContext();
        //     const { wait, done } = createBarrier();
        //     registerProvider(extensionHost)(
        //         ['*'],
        //         providerWithImpl((doc, pos: any) => {
        //             console.log('doc, pos', doc, pos);
        //             assertToJSON(doc, { uri: { $mid: 1, path: '/f', scheme: 'file' }, languageId: 'l', text: 't' });
        //             if (pos && pos.line) { assertToJSON(pos, { line: 0, character: 1 }); }
        //             if (pos && pos.tabSize) { assertToJSON(pos, { tabSize: 2, insertSpaces: true }); }
        //             done();
        //         }),
        //     );
        //     await extensionHost.internal.sync();
        //     await getResult(services)
        //         .pipe(take(1))
        //         .toPromise();

        //     await wait;
        // });
    });
}



/**
 * Generates test cases for cdecode.languages.registerXyzProvider functions and their associated
 * XyzProviders, for providers that return a list of locations.
 */
function testLocationProviderDoc<P>(
    name: keyof typeof cdecodeLanguages,
    registerProvider: (
        extensionHost: typeof cdecode,
    ) => (selector: cdecode.DocumentSelector, provider: P) => cdecode.Unsubscribable,
    labeledProvider: (label: string) => P,
    labeledProviderResults: (lables: string[]) => any,
    providerWithImpl: (run: (doc: cdecode.TextDocument) => void) => P,
    getResult: (services: IServices) => Observable<any>,
): void {
    describe(`languages.${name}`, () => {
        test('registers and unregisters a single provider', async () => {
            const { services, extensionHost } = await integrationTestContext();

            // Register the provider and call it.
            const unsubscribe = registerProvider(extensionHost)(['*'], labeledProvider('a'));
            await extensionHost.internal.sync();
            const res = await getResult(services)
                .pipe(
                    take(1),
                ).toPromise();
            console.log('res=====>', res);
            expect(
                res,
            ).toEqual(labeledProviderResults(['a']));

            // Unregister the provider and ensure it's removed.
            unsubscribe.unsubscribe();
            expect(
                await getResult(services)
                    .pipe(take(1))
                    .toPromise(),
            ).toEqual(null);
        });

        // test('supplies params to the provideXyz method', async () => {
        //     const { services, extensionHost } = await integrationTestContext();
        //     const { wait, done } = createBarrier();
        //     registerProvider(extensionHost)(
        //         ['*'],
        //         providerWithImpl((doc) => {
        //             assertToJSON(doc, { uri: { $mid: 1, path: '/f', scheme: 'file' }, languageId: 'l', text: 't' });
        //             done();
        //         }),
        //     );
        //     await extensionHost.internal.sync();
        //     await getResult(services)
        //         .pipe(take(1))
        //         .toPromise();

        //     await wait;
        // });
    });
}


function labeledDefinitionResults(labels: string[]): cdecode.Location[] {
    return labels.map(label => ({ uri: URI.from({ path: `file:///${label}`, scheme: 'file' }), range: undefined }));
}

function documentSymbolsResults(labels: string[]) {
    return [{
        name: 'test',
        detail: 'test',
        kind: 1,
        range: new Range(1, 1, 1, 1),
        selectionRange: new Range(1, 1, 1, 1),
        children: [],
    }];
}

function documentHighLight() {
    return [{'eol': undefined, 'range': undefined}];
}

function сodeActions() {
    return [{'command': undefined, 'diagnostics': undefined, 'edit': undefined, 'kind': undefined, 'title': 'test'}];
}

function formattingEditProvider() {
    return [{'eol': undefined, 'range': undefined, 'text': 'some'}];
}

function renaneProvider() {
    return [{
        edits: [],
    }];
}

function completionItemProvider() {
    return [{'incomplete': undefined, 'suggestions': []}];
}

function foldingRangeProvider() {
    return [[{'additionalTextEdits': undefined, 'label': undefined, 'textEdit': undefined}]];
}

function provideWorkspaceSymbols() {
    return [];
}

function workspaceSymbolProvider() {
    return [[{'additionalTextEdits': undefined, 'label': undefined, 'textEdit': undefined}]];
}
