/* eslint-disable no-case-declarations */
/*---------------------------------------------------------
 * Copyright (C) Microsoft Corporation. All rights reserved.
 * Modification copyright 2020 The Go Authors. All rights reserved.
 * Licensed under the MIT License. See LICENSE in the project root for license information.
 *--------------------------------------------------------*/

'use strict';

import vscode = require('vscode');
import vscodeUri = require('vscode-uri');
import { getGoConfig } from './config';
import { formatGoVersion, GoEnvironmentOption, terminalCreationListener } from './goEnvironmentStatus';
import { GoDocumentSelector, isGoFile } from './goMode';
import { runGoEnv } from './goModules';
import { allToolsInformation } from './goToolsInformation';
import { getGoVersion } from './util';
import { GoExtensionContext } from './context';
import { CommandFactory } from './commands';
import { LanguageClient, State } from 'vscode-languageclient/node';

export const outputChannel = vscode.window.createOutputChannel('Go', { log: true });

const STATUS_BAR_ITEM_NAME = 'Go Diagnostics';
export const diagnosticsStatusBarItem = vscode.window.createStatusBarItem(
	STATUS_BAR_ITEM_NAME,
	vscode.StatusBarAlignment.Left
);
diagnosticsStatusBarItem.name = STATUS_BAR_ITEM_NAME;

// statusbar item for switching the Go environment
export let goEnvStatusbarItem: vscode.StatusBarItem;

let gomod: string;
let gowork: string;
const languageServerIcon = '$(zap)';
const languageServerErrorIcon = '$(warning)';
const languageServerStartingIcon = '$(sync~spin)';

export async function updateGoStatusBar(editor: vscode.TextEditor | undefined) {
	if (!editor) {
		return;
	}
	if (isGoFile(editor.document)) {
		showGoStatusBar();
		return;
	}
	if (editor.document.languageId?.toLowerCase() !== 'log') {
		goEnvStatusbarItem.hide();
	}
}

export const expandGoStatusBar: CommandFactory = (ctx, goCtx) => async () => {
	// Only update the module path if we are in a Go file.
	// This allows the user to open output windows without losing
	// the go.mod information in the status bar.
	const editor = vscode.window.activeTextEditor;
	if (!!editor && isGoFile(editor.document)) {
		const cwd = vscodeUri.Utils.dirname(editor.document.uri);
		try {
			const p = await runGoEnv(cwd, ['GOMOD', 'GOWORK']);
			gomod = p['GOMOD'] === '/dev/null' || p['GOMOD'] === 'NUL' ? '' : p['GOMOD'];
			gowork = p['GOWORK'];
		} catch (e) {
			outputChannel.debug(`failed to run go env from ${cwd} - ${e}`);
		}
	}

	const { languageServerIsRunning, serverOutputChannel } = goCtx;
	const options = [
		{ label: 'Locate Configured Go Tools', description: 'display go env' },
		{ label: 'Choose Go Environment' }
	];

	const cfg = goCtx.latestConfig;
	// Get the gopls configuration.
	const goConfig = getGoConfig();
	const goplsIsRunning = languageServerIsRunning && cfg && cfg.serverName === 'gopls';
	if (goplsIsRunning) {
		const goplsVersion = cfg.version;
		options.push({ label: `${languageServerIcon} Open 'gopls' trace`, description: `${goplsVersion?.version}` });
	}
	// In case gopls still need to be installed, cfg.serverName will be empty.
	if (!goplsIsRunning && goConfig.get('useLanguageServer') === true && cfg?.serverName === '') {
		options.push({
			label: 'Install Go Language Server',
			description: `${languageServerErrorIcon}'gopls' is required but missing`
		});
	}

	// If modules is enabled, add link to mod file
	if (gomod) {
		options.push({ label: "Open 'go.mod'", description: gomod });
	}

	if (gowork) {
		options.push({ label: "Open 'go.work'", description: gowork });
	}

	vscode.window.showQuickPick(options).then((item) => {
		if (item) {
			switch (item.label) {
				case 'Locate Configured Go Tools':
					vscode.commands.executeCommand('go.locate.tools');
					break;
				case 'Choose Go Environment':
					vscode.commands.executeCommand('go.environment.choose');
					break;
				case `${languageServerIcon} Open 'gopls' trace`:
					if (serverOutputChannel) {
						serverOutputChannel.show();
					}
					break;
				case 'Install Go Language Server':
					vscode.commands.executeCommand('go.tools.install', [allToolsInformation['gopls']]);
					break;
				case "Open 'go.work'":
				case "Open 'go.mod'":
					if (item.description) {
						const openPath = vscode.Uri.file(item.description);
						vscode.workspace.openTextDocument(openPath).then((doc) => {
							vscode.window.showTextDocument(doc);
						});
						break;
					}
			}
		}
	});
};

/**
 * Initialize the status bar item with current Go binary
 */
export async function initGoStatusBar(goCtx: GoExtensionContext) {
	const { languageClient } = goCtx;
	if (!goEnvStatusbarItem) {
		const STATUS_BAR_ITEM_NAME = 'Go';
		goEnvStatusbarItem = vscode.window.createStatusBarItem(
			STATUS_BAR_ITEM_NAME,
			vscode.StatusBarAlignment.Right,
			100.09999 // place the item right after the language status item https://github.com/microsoft/vscode-python/issues/18040#issuecomment-992567670.
		);
		goEnvStatusbarItem.name = STATUS_BAR_ITEM_NAME;
	}
	// set Go version and command
	const version = await getGoVersion();
	const goOption = new GoEnvironmentOption(version.binaryPath, formatGoVersion(version));

	goEnvStatusbarItem.text = goOption.label;
	goEnvStatusbarItem.command = 'go.environment.status';

	// Add an icon to indicate that the 'gopls' server is running.
	// Assume if it is configured it is already running, since the
	// icon will be updated on an attempt to start.
	const goConfig = getGoConfig();
	updateLanguageServerIconGoStatusBar(languageClient, goConfig['useLanguageServer'] === true);
	if (vscode.window.visibleTextEditors.some((editor) => !!editor && isGoFile(editor.document))) {
		showGoStatusBar();
	}
}

export function updateLanguageServerIconGoStatusBar(languageClient: LanguageClient | undefined, enabled: boolean) {
	if (!goEnvStatusbarItem) {
		return;
	}

	// Split the existing goEnvStatusbarItem.text into the version string part and
	// the gopls icon part.
	let text = goEnvStatusbarItem.text;
	if (text.endsWith(languageServerIcon)) {
		text = text.substring(0, text.length - languageServerIcon.length);
	} else if (text.endsWith(languageServerErrorIcon)) {
		text = text.substring(0, text.length - languageServerErrorIcon.length);
	} else if (text.endsWith(languageServerStartingIcon)) {
		text = text.substring(0, text.length - languageServerStartingIcon.length);
	}
	let color = undefined;
	let icon = '';
	if (!enabled || !languageClient) {
		icon = '';
		color = new vscode.ThemeColor('statusBarItem.warningBackground');
	} else if (languageClient.state === State.Starting) {
		icon = languageServerStartingIcon;
		color = undefined;
	} else if (languageClient.state === State.Running) {
		icon = languageServerIcon;
		color = undefined;
	} else if (languageClient.state === State.Stopped) {
		icon = languageServerErrorIcon;
		color = new vscode.ThemeColor('statusBarItem.errorBackground');
	}

	goEnvStatusbarItem.text = text + icon;
	goEnvStatusbarItem.backgroundColor = color;
}

/**
 * disable the Go status bar items
 */
export function disposeGoStatusBar() {
	if (goEnvStatusbarItem) {
		goEnvStatusbarItem.dispose();
	}
	if (terminalCreationListener) {
		terminalCreationListener.dispose();
	}
	for (const statusBarEntry of statusBarEntries) {
		if (statusBarEntry) {
			const [name, entry] = statusBarEntry;
			statusBarEntries.delete(name);
			entry.dispose();
		}
	}
}

/**
 * Show the Go statusbar items on the statusbar
 */
export function showGoStatusBar() {
	if (goEnvStatusbarItem) {
		goEnvStatusbarItem.show();
	}
}

// status bar item to show warning messages such as missing analysis tools.
const statusBarEntries = new Map<string, vscode.LanguageStatusItem>();

export function removeGoStatus(name: string) {
	const statusBarEntry = statusBarEntries.get(name);
	if (statusBarEntry) {
		statusBarEntry.dispose();
		statusBarEntries.delete(name);
	}
}

export function addGoStatus(name: string): vscode.LanguageStatusItem {
	let statusBarEntry = statusBarEntries.get(name);
	if (!statusBarEntry) {
		statusBarEntry = vscode.languages.createLanguageStatusItem(name, GoDocumentSelector);
		statusBarEntries.set(name, statusBarEntry);
	}
	return statusBarEntry;
}
