/* tslint:disable */
import 'reflect-metadata';
import { Subscription, Unsubscribable } from 'rxjs';
import * as cdecode from 'cdecode/client';
import gql from 'graphql-tag';
import { createProxy, handleRequests, handleServerProxyRequests } from '@workbench-stack/extension-api';
import { Connection, createConnection, Logger, MessageTransports } from '@workbench-stack/extension-api';
import { WebExtHostCommands } from './ext-host-commands';
import { ExtHostContext } from './ext-host-context';
import { WebExtHostExtensionService } from './ext-host-extension-service';
import { WebExtHostConfiguration } from './ext-host-client-configuration';
import { ExtHostDocuments } from './ext-host-documents';
import { WebExtHostViews } from './ext-host-view';
import { IClientInitData } from '@workbench-stack/extension-api';
import * as extHostTypes from '@workbench-stack/extension-api/lib/core/types';
import ApolloClient from 'apollo-client';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { WebSocketLink } from 'apollo-link-ws';
import { InMemoryCache } from 'apollo-cache-inmemory';
// import { pkg } from '@workbench-stack/server-core';
import { WebExtHostLanguageFeatures } from './ext-language-features';
import { URI } from '@vscode/monaco-editor/esm/vs/base/common/uri';
import * as typeConverters from '@workbench-stack/extension-api/lib/core/converters/ext-host-type-converters';
import { score } from '@vscode/monaco-editor/esm/vs/editor/common/modes/languageSelector';
import { ExtHostContext as ServerExtHostContext, MainContext } from '@workbench-stack/extension-api/lib/protocol/server.protocol';
import { RPCProtocol } from '@workbench-stack/extension-api/lib/protocol/rpc-protocol';
import { RPCLogger } from '@workbench-stack/extension-api/lib/protocol/rpc-logger';
import { BrowserRemoteRPC } from '@workbench-stack/extension-api/lib/connections/remote-rpc';
import { HostIdentifier } from '@workbench-stack/extension-api/lib/protocol/proxy-identifier';
import { TextEditorCursorStyle } from '@vscode/monaco-editor/esm/vs/editor/common/config/editorOptions';
import { WebExtHostEditorWindows } from './ext-editor-windows';
import { ExtHostDocumentsAndEditors } from './ext-host-documents-and-editors';


const consoleLogger: Logger = {
    error(message: string): void {
        console.error(message);
    },
    warn(message: string): void {
        console.warn(message);
    },
    info(message: string): void {
        console.info(message);
    },
    log(message: string): void {
        console.log(message);
    },
};


/**
 * Starts the extension host, which runs extensions. It is a Web Worker or other similar isolated
 * JavaScript execution context. There is exactly 1 extension hsot, and it has zeor or more
 * extensions activated (and running).
 *
 * It expects to receive a message containing {@link IInitData} from the client application as the
 * first message.
 *
 * @param transports The message reader and writer to use for communication with the client.
 * @return An unsubscribable to terminate the extension host.
 */
export function startExtensionHost(transports: MessageTransports, enableBackendConnection: boolean = false): Unsubscribable & { __testAPI: Promise<typeof cdecode> } {

    console.log('startExtensionHost:>', { transports });

    const connection = createConnection(transports, consoleLogger);
    connection.listen();

    const subscription = new Subscription();
    subscription.add(connection);

    // Wait for "initialize" message from client application before proceeding to create the
    // extension host.
    let initialized = false;
    const __testAPI = new Promise<typeof cdecode>(resolve => {
        connection.onRequest('initialize', async ([initData]: [IClientInitData]) => {
            if (initialized) {
                throw new Error('extension host is already initialized');
            }
            initialized = true;
            let serverApi, remoteRPC

            if (enableBackendConnection) {
                // graphql
                // setup websocket connection
                const apolloSubscription = new SubscriptionClient(initData.graphqlSubscriptionUrl, {
                    reconnect: true,
                });
                const link = new WebSocketLink(apolloSubscription);
                serverApi = new ApolloClient({
                    link,
                    cache: new InMemoryCache(),
                });

                
                const remoteConnection = new BrowserRemoteRPC();
                remoteConnection.connect(initData.extensionSocketUrl, () => {
                    const socket = new WebSocket(initData.extensionSocketUrl);
                    socket.binaryType = 'arraybuffer';
                    return socket;
                });
                remoteRPC = new RPCProtocol(remoteConnection, HostIdentifier.BrowserExtHost, new RPCLogger(), null)

            }
            const { unsubscribe, __testAPI } = initializeExtensionHost(connection, initData, remoteRPC, serverApi);

            subscription.add(unsubscribe);
            resolve(__testAPI);
        });

    });

    return { unsubscribe: () => subscription.unsubscribe(), __testAPI };
}

/**
 * Initializes the extension host using the {@link IInitData} from the client application. It is
 * called by {@link startExtensionHost} after the {@link IInitData} is received.
 *
 * The extension API is made globally avaialble to all requires/imports of the "cdecode" module
 * by other scripts running in the same JavaScript context.
 * @param connection The connection used to communicate with client.
 * @param initData The information to initialize theis extension host.
 * @return An unsubscribable to terminate the extension host.
 */
function initializeExtensionHost(connection: Connection, initData: IClientInitData, remoteRPC: RPCProtocol, serverApi): Unsubscribable & { __testAPI: typeof cdecode } {
    const subscriptions = new Subscription();

    // Workaround for different envs
    var globalObject =
        typeof window !== 'undefined' ? window :
            typeof self !== 'undefined' ? self :
                typeof global !== 'undefined' ? global :
                    {};

    const { api, subscription: apiSubscription } = createExtensionAPI(initData, connection, remoteRPC, serverApi);
    subscriptions.add(apiSubscription);

    // Make `import 'cdecode'` or `require('cdecode')` return the extension API.
    (globalObject as any).require = (modulePath: string): any => {
        if (modulePath === 'cdecode/client') {
            return api;
        }
        // All other requires/imports in the extenion's code should not reach here because their JS
        // bundler should have resolved them locally.
        throw new Error(`require: module not found: ${modulePath}`);
    };
    subscriptions.add(() => {
        (globalObject as any).require = () => {
            // Prevent callers from attempting to access the extension API after it was
            // unsubscribed.
            throw new Error(`require: Cdecode extension API was unsubscribed`);
        };
    });

    return { unsubscribe: () => subscriptions.unsubscribe(), __testAPI: api };
}

function createExtensionAPI(initData: IClientInitData, connection: Connection, rpcProtocol: RPCProtocol, serverApi): { api: typeof cdecode; subscription: Subscription } {
    const subscriptions = new Subscription();

    // For debugging/tests.
    const sync = () => connection.sendRequest<void>('ping');
    connection.onRequest('ping', () => 'pong');

    const context = new ExtHostContext(createProxy(connection, 'context'));
    handleRequests(connection, 'context', context);

    const views = new WebExtHostViews(createProxy(connection, 'views'));
    handleRequests(connection, 'views', views);

    const windows = new WebExtHostEditorWindows(createProxy(connection, 'editor-windows'));
    handleRequests(connection, 'editor-windows', windows);

    // const documents = new ExtHostDocuments(sync);
    // handleRequests(connection, 'documents', documents);

    const extensions = new WebExtHostExtensionService();
    subscriptions.add(extensions);
    handleRequests(connection, 'extensions', extensions);

    const commands = new WebExtHostCommands(createProxy(connection, 'commands'));
    subscriptions.add(commands);
    handleRequests(connection, 'commands', commands);

    const configuration = new WebExtHostConfiguration<any>(createProxy(connection, 'configuration'));
    handleRequests(connection, 'configuration', configuration);


    const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(connection, createProxy(connection, 'documentsAndEditors'));
    handleRequests(connection, 'documentsAndEditors', extHostDocumentsAndEditors);

    const extHostDocuments = new ExtHostDocuments(createProxy(connection, 'documents'), extHostDocumentsAndEditors);
    handleRequests(connection, 'documents', extHostDocuments);

    const extHostLanguageFeatures = new WebExtHostLanguageFeatures(createProxy(connection, 'languageFeatures'), extHostDocuments);
    subscriptions.add(extHostLanguageFeatures);
    handleRequests(connection, 'languageFeatures', extHostLanguageFeatures);

    if (rpcProtocol) {
        // remote connections
        // commneted as of 05/17/2019
        handleServerProxyRequests(connection, rpcProtocol, ServerExtHostContext);

        /**
         * Register to the ClientThread classes via `Connection`.
         * The request from the server side ExtHost will be directly forwarded to Client Thread.
         */
        rpcProtocol.set(MainContext.MainThreadTreeViews, createProxy(connection, 'treeViews'));
        rpcProtocol.set(MainContext.MainThreadLanguageFeatures, createProxy(connection, 'languageFeatures'));
    }


    // namespace: languages
    const languages: typeof cdecode.languages = {

        // createDiagnosticCollection(name?: string): cdecode.DiagnosticCollection {
        //     return extHostDiagnostics.createDiagnosticCollection(name);
        // },
        // get onDidChangeDiagnostics() {
        //     return extHostDiagnostics.onDidChangeDiagnostics;
        // },
        // getDiagnostics: (resource?: cdecode.Uri) => {
        //     return <any>extHostDiagnostics.getDiagnostics(resource);
        // },
        // getLanguages(): Promise<string[]> {
        //     return extHostLanguages.getLanguages();
        // },
        // setTextDocumentLanguage(document: cdecode.TextDocument, languageId: string): Promise<cdecode.TextDocument> {
        //     return extHostLanguages.changeLanguage(document.uri, languageId);
        // },
        match(selector: cdecode.DocumentSelector, document: cdecode.TextDocument): number {
            return score(typeConverters.LanguageSelector.from(selector), document.uri, document.languageId, true);
        },
        registerCodeActionsProvider(selector: cdecode.DocumentSelector, provider: cdecode.CodeActionProvider, metadata?: cdecode.CodeActionProviderMetadata) {
            return extHostLanguageFeatures.registerCodeActionProvider(selector, provider/**, extension, metadata */);
        },
        registerCodeLensProvider(selector: cdecode.DocumentSelector, provider: cdecode.CodeLensProvider) {
            return extHostLanguageFeatures.registerCodeLensProvider(selector, provider);
        },
        registerDefinitionProvider(selector: cdecode.DocumentSelector, provider: cdecode.DefinitionProvider) {
            return extHostLanguageFeatures.registerDefinitionProvider(selector, provider);
        },
        registerImplementationProvider(selector: cdecode.DocumentSelector, provider: cdecode.ImplementationProvider) {
            return extHostLanguageFeatures.registerImplementationProvider(selector, provider);
        },
        registerTypeDefinitionProvider(selector: cdecode.DocumentSelector, provider: cdecode.TypeDefinitionProvider) {
            return extHostLanguageFeatures.registerTypeDefinitionProvider(selector, provider);
        },
        registerHoverProvider(selector: cdecode.DocumentSelector, provider: cdecode.HoverProvider) {
            console.log('[hed834ecd.selector]: upd', { selector, provider });
            return extHostLanguageFeatures.registerHoverProvider(selector, provider/**, extension.identifier */);
        },
        registerDocumentHighlightProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentHighlightProvider) {
            return extHostLanguageFeatures.registerDocumentHighlightProvider(selector, provider);
        },
        registerReferenceProvider(selector: cdecode.DocumentSelector, provider: cdecode.ReferenceProvider) {
            return extHostLanguageFeatures.registerReferenceProvider(selector, provider);
        },
        registerRenameProvider(selector: cdecode.DocumentSelector, provider: cdecode.RenameProvider) {
            return extHostLanguageFeatures.registerRenameProvider(selector, provider, true);
        },
        registerDocumentSymbolProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentSymbolProvider) {
            return extHostLanguageFeatures.registerDocumentSymbolProvider(selector, provider/**, extension */);
        },
        registerWorkspaceSymbolProvider(provider: cdecode.WorkspaceSymbolProvider) {
            return extHostLanguageFeatures.registerWorkspaceSymbolProvider(provider);
        },
        registerDocumentFormattingEditProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentFormattingEditProvider) {
            return extHostLanguageFeatures.registerFormattingProvider(selector, provider);
        },
        registerDocumentRangeFormattingEditProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentRangeFormattingEditProvider) {
            return extHostLanguageFeatures.registerDocumentRangeFormattingEditProvider(selector, provider);
        },
        registerOnTypeFormattingEditProvider(selector: cdecode.DocumentSelector, provider: cdecode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]) {
            return extHostLanguageFeatures.registerOnTypeFormattingEditProvider(selector, provider, [firstTriggerCharacter].concat(moreTriggerCharacters));
        },
        registerSignatureHelpProvider(selector: cdecode.DocumentSelector, provider: cdecode.SignatureHelpProvider, firstItem?: string | cdecode.SignatureHelpProviderMetadata, ...remaining: string[]) {
            if (typeof firstItem === 'object') {
                return extHostLanguageFeatures.registerSignatureHelpProvider(selector, provider, firstItem);
            }
            return extHostLanguageFeatures.registerSignatureHelpProvider(selector, provider, typeof firstItem === 'undefined' ? [] : [firstItem, ...remaining]);
        },
        registerCompletionItemProvider(selector: cdecode.DocumentSelector, provider: cdecode.CompletionItemProvider, ...triggerCharacters: string[]) {
            return extHostLanguageFeatures.registerCompletionItemProvider(selector, provider, triggerCharacters);
        },
        registerDocumentLinkProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentLinkProvider) {
            return extHostLanguageFeatures.registerDocumentLinkProvider(selector, provider);
        },
        registerColorProvider(selector: cdecode.DocumentSelector, provider: cdecode.DocumentColorProvider) {
            return extHostLanguageFeatures.registerColorProvider(selector, provider);
        },
        registerFoldingRangeProvider(selector: cdecode.DocumentSelector, provider: cdecode.FoldingRangeProvider) {
            return extHostLanguageFeatures.registerFoldingRangeProvider(selector, provider);
        },
        setLanguageConfiguration: (language: string, configuration: cdecode.LanguageConfiguration) => {
            return extHostLanguageFeatures.setLanguageConfiguration(language, configuration);
        }
    };
    const api: typeof cdecode = {
        // version: pkg.version,
        commands: {
            registerCommand: (command, callback) => commands.registerCommand({ command, callback }),
            executeCommand: (command, ...args) => commands.executeCommand(command, args),
        },
        configuration: {
            get: () => configuration.get(),
            subscribe: (next: () => void) => configuration.subscribe(next),
        },
        languages,
        // types

        CodeAction: extHostTypes.CodeAction,
        CodeActionKind: extHostTypes.CodeActionKind,
        CodeLens: extHostTypes.CodeLens,
        Color: extHostTypes.Color,
        ColorInformation: extHostTypes.ColorInformation,
        ColorPresentation: extHostTypes.ColorPresentation,
        // CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState,
        CompletionItem: extHostTypes.CompletionItem,
        CompletionItemKind: extHostTypes.CompletionItemKind,
        CompletionItemInsertTextRule: extHostTypes.CompletionItemInsertTextRule,
        CompletionList: extHostTypes.CompletionList,
        CompletionTriggerKind: extHostTypes.CompletionTriggerKind,
        ConfigurationTarget: extHostTypes.ConfigurationTarget,
        Diagnostic: extHostTypes.Diagnostic,
        DocumentHighlight: extHostTypes.DocumentHighlight,
        DocumentHighlightKind: extHostTypes.DocumentHighlightKind,
        DocumentLink: extHostTypes.DocumentLink,
        DocumentSymbol: extHostTypes.DocumentSymbol,
        EndOfLine: extHostTypes.EndOfLine,
        FoldingRange: extHostTypes.FoldingRange,
        // FoldingRangeKind: extHostTypes.FoldingRangeKind,
        // FunctionBreakpoint: extHostTypes.FunctionBreakpoint,
        Hover: extHostTypes.Hover,
        // IndentAction: languageConfiguration.IndentAction,
        Location: extHostTypes.Location,
        LogLevel: extHostTypes.LogLevel,
        MarkdownString: extHostTypes.MarkdownString,
        // OverviewRulerLane: OverviewRulerLane,
        // ParameterInformation: extHostTypes.ParameterInformation,
        Position: extHostTypes.Position,
        // ProcessExecution: extHostTypes.ProcessExecution,
        // ProgressLocation: extHostTypes.ProgressLocation,
        // QuickInputButtons: extHostTypes.QuickInputButtons,
        Range: extHostTypes.Range,
        RelativePattern: extHostTypes.RelativePattern,
        Selection: extHostTypes.Selection,
        // ShellExecution: extHostTypes.ShellExecution,
        // ShellQuoting: extHostTypes.ShellQuoting,
        // SignatureHelpTriggerReason: extHostTypes.SignatureHelpTriggerReason,
        SignatureHelp: extHostTypes.SignatureHelp,
        SignatureInformation: extHostTypes.SignatureInformation,
        // SnippetString: extHostTypes.SnippetString,
        // SourceBreakpoint: extHostTypes.SourceBreakpoint,
        // SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
        // StatusBarAlignment: extHostTypes.StatusBarAlignment,
        SymbolInformation: extHostTypes.SymbolInformation,
        SymbolKind: extHostTypes.SymbolKind,
        // Task: extHostTypes.Task,
        // TaskGroup: extHostTypes.TaskGroup,
        // TaskPanelKind: extHostTypes.TaskPanelKind,
        // TaskRevealKind: extHostTypes.TaskRevealKind,
        // TaskScope: extHostTypes.TaskScope,
        TextDocumentSaveReason: extHostTypes.TextDocumentSaveReason,
        TextEdit: extHostTypes.TextEdit,
        TextEditorCursorStyle: TextEditorCursorStyle,
        TextEditorLineNumbersStyle: extHostTypes.TextEditorLineNumbersStyle,
        TextEditorRevealType: extHostTypes.TextEditorRevealType,
        TextEditorSelectionChangeKind: extHostTypes.TextEditorSelectionChangeKind,
        ThemeColor: extHostTypes.ThemeColor,
        ThemeIcon: extHostTypes.ThemeIcon,
        // TreeItem: extHostTypes.TreeItem,
        // TreeItem2: extHostTypes.TreeItem,
        // TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
        URI: URI,
        // ViewColumn: extHostTypes.ViewColumn,
        WorkspaceEdit: extHostTypes.WorkspaceEdit,
        internal: {
            udpateContext: (updates: cdecode.ContextValues) => context.updateContext(updates),
            cdecodeURL: URI.parse(initData.cdecodeURL),
            clientApplication: initData.clientApplication,
            sync,
            serverApi,
            gql,
        }
    };
    return { api, subscription: subscriptions };
}
