/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
/*
 * Copyright (C) 2017, 2018 TypeFox and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 */
import { PrefixingLogger } from './utils/logger.js';
import API from './utils/api.js';
import { ServerResponse } from './tsServer/requests.js';
import { TypeScriptServerError } from './tsServer/serverError.js';
import { TypeScriptServerSpawner } from './tsServer/spawner.js';
import Tracer from './tsServer/tracer.js';
class ServerInitializingIndicator {
    constructor(lspClient) {
        this.lspClient = lspClient;
    }
    reset() {
        if (this._loadingProjectName) {
            this._loadingProjectName = undefined;
            if (this._progressReporter) {
                this._progressReporter.done();
                this._progressReporter = undefined;
            }
        }
    }
    async startedLoadingProject(projectName) {
        // TS projects are loaded sequentially. Cancel existing task because it should always be resolved before
        // the incoming project loading task is.
        this.reset();
        this._loadingProjectName = projectName;
        this._progressReporter = await this.lspClient.createProgressReporter();
        this._progressReporter.begin('Initializing JS/TS language features…');
    }
    finishedLoadingProject(projectName) {
        if (this._loadingProjectName === projectName) {
            this._loadingProjectName = undefined;
            if (this._progressReporter) {
                this._progressReporter.done();
                this._progressReporter = undefined;
            }
        }
    }
}
export class TspClient {
    constructor(options) {
        this.options = options;
        this.primaryTsServer = null;
        this.apiVersion = options.typescriptVersion.version || API.defaultVersion;
        this.logger = new PrefixingLogger(options.logger, '[tsclient]');
        this.tsserverLogger = new PrefixingLogger(options.logger, '[tsserver]');
        this.loadingIndicator = new ServerInitializingIndicator(options.lspClient);
        this.tracer = new Tracer(this.tsserverLogger, options.trace);
    }
    start() {
        const tsServerSpawner = new TypeScriptServerSpawner(this.apiVersion, this.options.logDirectoryProvider, this.logger, this.tracer);
        const tsServer = tsServerSpawner.spawn(this.options.typescriptVersion, this.options);
        tsServer.onExit((data) => {
            this.shutdown();
            this.tsserverLogger.error(`Exited. Code: ${data.code}. Signal: ${data.signal}`);
            if (this.options.onExit) {
                this.options.onExit(data.code, data.signal);
            }
        });
        tsServer.onError((err) => {
            if (err) {
                this.tsserverLogger.error('Exited with error. Error message is: {0}', err.message || err.name);
            }
            this.serviceExited();
        });
        tsServer.onEvent(event => this.dispatchEvent(event));
        this.primaryTsServer = tsServer;
        return true;
    }
    serviceExited() {
        this.primaryTsServer = null;
        this.loadingIndicator.reset();
    }
    dispatchEvent(event) {
        switch (event.event) {
            case "syntaxDiag" /* EventTypes.SyntaxDiag */:
            case "semanticDiag" /* EventTypes.SementicDiag */:
            case "suggestionDiag" /* EventTypes.SuggestionDiag */: {
                // This event also roughly signals that projects have been loaded successfully (since the TS server is synchronous)
                this.loadingIndicator.reset();
                this.options.onEvent?.(event);
                break;
            }
            // case EventTypes.ConfigFileDiag:
            //     this._onConfigDiagnosticsReceived.fire(event as tsp.ConfigFileDiagnosticEvent);
            //     break;
            // case EventTypes.projectLanguageServiceState: {
            //     const body = (event as tsp.ProjectLanguageServiceStateEvent).body!;
            //     if (this.serverState.type === ServerState.Type.Running) {
            //         this.serverState.updateLanguageServiceEnabled(body.languageServiceEnabled);
            //     }
            //     this._onProjectLanguageServiceStateChanged.fire(body);
            //     break;
            // }
            // case EventTypes.projectsUpdatedInBackground: {
            //     this.loadingIndicator.reset();
            //     const body = (event as tsp.ProjectsUpdatedInBackgroundEvent).body;
            //     const resources = body.openFiles.map(file => this.toResource(file));
            //     this.bufferSyncSupport.getErr(resources);
            //     break;
            // }
            // case EventTypes.beginInstallTypes:
            //     this._onDidBeginInstallTypings.fire((event as tsp.BeginInstallTypesEvent).body);
            //     break;
            // case EventTypes.endInstallTypes:
            //     this._onDidEndInstallTypings.fire((event as tsp.EndInstallTypesEvent).body);
            //     break;
            // case EventTypes.typesInstallerInitializationFailed:
            //     this._onTypesInstallerInitializationFailed.fire((event as tsp.TypesInstallerInitializationFailedEvent).body);
            //     break;
            case "projectLoadingStart" /* EventTypes.ProjectLoadingStart */:
                this.loadingIndicator.startedLoadingProject(event.body.projectName);
                break;
            case "projectLoadingFinish" /* EventTypes.ProjectLoadingFinish */:
                this.loadingIndicator.finishedLoadingProject(event.body.projectName);
                break;
        }
    }
    shutdown() {
        if (this.loadingIndicator) {
            this.loadingIndicator.reset();
        }
        if (this.primaryTsServer) {
            this.primaryTsServer.kill();
        }
    }
    notify(command, args) {
        this.executeWithoutWaitingForResponse(command, args);
    }
    requestGeterr(args, token) {
        return this.executeAsync("geterr" /* CommandTypes.Geterr */, args, token);
    }
    request(command, args, token, config) {
        return this.execute(command, args, token, config);
    }
    // Low-level API.
    execute(command, args, token, config) {
        let executions;
        // if (config?.cancelOnResourceChange) {
        //     if (this.primaryTsServer) {
        //         const source = new CancellationTokenSource();
        //         token.onCancellationRequested(() => source.cancel());
        //         const inFlight: ToCancelOnResourceChanged = {
        //             resource: config.cancelOnResourceChange,
        //             cancel: () => source.cancel(),
        //         };
        //         runningServerState.toCancelOnResourceChange.add(inFlight);
        //         executions = this.executeImpl(command, args, {
        //             isAsync: false,
        //             token: source.token,
        //             expectsResult: true,
        //             ...config,
        //         });
        //         executions[0]!.finally(() => {
        //             runningServerState.toCancelOnResourceChange.delete(inFlight);
        //             source.dispose();
        //         });
        //     }
        // }
        if (!executions) {
            executions = this.executeImpl(command, args, {
                isAsync: false,
                token,
                expectsResult: true,
                ...config,
            });
        }
        if (config?.nonRecoverable) {
            executions[0].catch(err => this.fatalError(command, err));
        }
        if (command === "updateOpen" /* CommandTypes.UpdateOpen */) {
            // If update open has completed, consider that the project has loaded
            Promise.all(executions).then(() => {
                this.loadingIndicator.reset();
            });
        }
        return executions[0];
    }
    executeWithoutWaitingForResponse(command, args) {
        this.executeImpl(command, args, {
            isAsync: false,
            token: undefined,
            expectsResult: false,
        });
    }
    executeAsync(command, args, token) {
        return this.executeImpl(command, args, {
            isAsync: true,
            token,
            expectsResult: true,
        })[0];
    }
    executeImpl(command, args, executeInfo) {
        if (this.primaryTsServer) {
            return this.primaryTsServer.executeImpl(command, args, executeInfo);
        }
        else {
            return [Promise.resolve(ServerResponse.NoServer)];
        }
    }
    fatalError(command, error) {
        this.tsserverLogger.error(`A non-recoverable error occurred while executing command: ${command}`);
        if (error instanceof TypeScriptServerError && error.serverErrorText) {
            this.tsserverLogger.error(error.serverErrorText);
        }
        if (this.primaryTsServer) {
            this.logger.info('Killing TS Server');
            this.primaryTsServer.kill();
            if (error instanceof TypeScriptServerError) {
                this.primaryTsServer = null;
            }
        }
    }
}
//# sourceMappingURL=tsp-client.js.map