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

// This code is modified from:
// https://github.com/microsoft/vscode-extension-samples/tree/master/webview-sample

import vscode = require('vscode');
import path = require('path');
import semver = require('semver');
import { extensionId } from './const';
import { GoExtensionContext } from './context';
import { extensionInfo, getGoConfig } from './config';
import { getFromGlobalState, updateGlobalState } from './stateUtils';
import { createRegisterCommand } from './commands';

export class WelcomePanel {
	public static activate(ctx: vscode.ExtensionContext, goCtx: GoExtensionContext) {
		const registerCommand = createRegisterCommand(ctx, goCtx);
		registerCommand('go.welcome', WelcomePanel.createOrShow);

		if (vscode.window.registerWebviewPanelSerializer) {
			// Make sure we register a serializer in activation event
			vscode.window.registerWebviewPanelSerializer(WelcomePanel.viewType, {
				async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel) {
					WelcomePanel.revive(webviewPanel, ctx.extensionUri);
				}
			});
		}

		// Show the Go welcome page on update unless one of the followings is true:
		//   * the extension is running in Cloud IDE or
		//   * the user explicitly opted out (go.showWelcome === false)
		//
		// It is difficult to write useful tests for this suppression logic
		// without major refactoring or complicating tests to enable
		// dependency injection or stubbing.
		if (!extensionInfo.isInCloudIDE && getGoConfig().get('showWelcome') !== false) {
			showGoWelcomePage();
		}
	}

	public static currentPanel: WelcomePanel | undefined;

	public static readonly viewType = 'welcomeGo';

	public static createOrShow(ctx: Pick<vscode.ExtensionContext, 'extensionUri'>) {
		return () => {
			const extensionUri = ctx.extensionUri;
			const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;

			// If we already have a panel, show it.
			if (WelcomePanel.currentPanel) {
				WelcomePanel.currentPanel.panel.reveal(column);
				return;
			}

			// Otherwise, create a new panel.
			const panel = vscode.window.createWebviewPanel(
				WelcomePanel.viewType,
				'Go for VS Code',
				column || vscode.ViewColumn.One,
				{
					// Enable javascript in the webview
					enableScripts: true,

					// And restrict the webview to only loading content from our extension's directory.
					localResourceRoots: [joinPath(extensionUri)]
				}
			);
			panel.iconPath = joinPath(extensionUri, 'media', 'go-logo-blue.png');

			WelcomePanel.currentPanel = new WelcomePanel(panel, extensionUri);
		};
	}

	public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
		WelcomePanel.currentPanel = new WelcomePanel(panel, extensionUri);
	}

	public readonly dataroot: vscode.Uri; // exported for testing.
	private readonly panel: vscode.WebviewPanel;
	private readonly extensionUri: vscode.Uri;
	private disposables: vscode.Disposable[] = [];

	private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
		this.panel = panel;
		this.extensionUri = extensionUri;
		this.dataroot = joinPath(this.extensionUri, 'media');

		// Set the webview's initial html content
		this.update();

		// Listen for when the panel is disposed
		// This happens when the user closes the panel or when the panel is closed programatically
		this.panel.onDidDispose(() => this.dispose(), null, this.disposables);

		// Handle messages from the webview
		this.panel.webview.onDidReceiveMessage(
			(message) => {
				console.log(message);
				switch (message.command) {
					case 'alert':
						vscode.window.showErrorMessage(message.text);
						return;
					case 'openDocument':
						const uri = joinPath(this.extensionUri, message.document);
						vscode.commands.executeCommand('markdown.showPreviewToSide', uri);
						return;
					case 'openSetting':
						vscode.commands.executeCommand('workbench.action.openSettings', message.setting);
						return;
				}
			},
			null,
			this.disposables
		);
	}

	public dispose() {
		WelcomePanel.currentPanel = undefined;

		// Clean up our resources
		this.panel.dispose();

		while (this.disposables.length) {
			const x = this.disposables.pop();
			if (x) {
				x.dispose();
			}
		}
	}

	private update() {
		const webview = this.panel.webview;
		this.panel.webview.html = this.getHtmlForWebview(webview);
	}

	private getHtmlForWebview(webview: vscode.Webview) {
		// Local path to css styles and images
		const scriptPathOnDisk = joinPath(this.dataroot, 'welcome.js');
		const stylePath = joinPath(this.dataroot, 'welcome.css');
		const gopherPath = joinPath(this.dataroot, 'go-logo-blue.png');
		const goExtension = vscode.extensions.getExtension(extensionId)!;
		const goExtensionVersion = goExtension.packageJSON.version;

		// Uri to load styles and images into webview
		const scriptURI = webview.asWebviewUri(scriptPathOnDisk);
		const stylesURI = webview.asWebviewUri(stylePath);
		const gopherURI = webview.asWebviewUri(gopherPath);

		// Use a nonce to only allow specific scripts to be run
		const nonce = getNonce();

		return `<!DOCTYPE html>
			<html lang="en">
			<head>
				<meta charset="UTF-8">
				<!--
					Use a content security policy to only allow loading images from https or from our extension directory,
					and only allow scripts that have a specific nonce.
				-->
				<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';">
				<meta name="viewport" content="width=device-width, initial-scale=1.0">
				<link href="${stylesURI}" rel="stylesheet">
				<title>Go for VS Code</title>
			</head>
			<body>
			<main class="Content">
			<div class="Header">
				<img src="${gopherURI}" alt="Go Logo" class="Header-logo"/>
				<div class="Header-details">
					<h1 class="Header-title">Go for VS Code v${goExtensionVersion}</h1>
					<p>The official Go extension for Visual Studio Code, providing rich language support for Go projects.</p>
					<ul class="Header-links">
						<!--
							Here and elsewhere, we must use a fake anchor for command buttons, to get styling
							consistent with links. We can't fake this using CSS, as it conflicts with theming.
						-->
						<li><a href="#" class="Command" data-command="openDocument" data-document="CHANGELOG.md">Release notes</a></li>
						<li><a href="https://github.com/golang/vscode-go">GitHub</a></li>
						<li><a href="https://stackoverflow.com/questions/tagged/go+visual-studio-code">Questions</a></li>
<li><a href="https://invite.slack.golangbridge.org/">Slack</a></li>
					</ul>
				</div>
			</div>

			<div class="Announcement">
				<p>📣 Recent gopls release (v0.14) includes <a href="https://go.dev/issue/58894">opt-in transparent telemetry</a>.
				</p>
				<p><b>Telemetry uploading is off by default</b> even when Visual Studio Code's telemetry setting is enabled.
				Go Telemetry needs to be enabled with the following command:
				<br>
				<code>go run golang.org/x/telemetry/cmd/gotelemetry@latest on</code>
				</p>
				<p>
				After telemetry is enabled, metrics and crash stack traces will be uploaded to <a href="https://telemetry.go.dev">telemetry.go.dev</a> weekly.
				You can see the preview of the collected data using:
				<br>
				<code>go run golang.org/x/telemetry/cmd/gotelemetry@latest view</code>
				</p>
				<p>
				If we get enough adoption, this data can significantly advance the pace of the go plugin development and help us meet a higher standard of reliability.
                Go telemetry is designed to be transparent and privacy-preserving. Learn more at <a href="https://go.dev/doc/telemetry">https://go.dev/doc/telemetry</a>.
				</p>
			</div>

			<div class="Cards">
				<div class="Card">
					<div class="Card-inner">
						<p class="Card-title">Getting started</p>
						<p class="Card-content">Learn about the Go extension in our
							<a href="https://github.com/golang/vscode-go/blob/master/README.md">README</a>.
						</p>
					</div>
				</div>

				<div class="Card">
					<div class="Card-inner">
						<p class="Card-title">Learning Go</p>
						<p class="Card-content">If you're new to the Go programming language,
							<a href="https://go.dev/learn">go.dev/learn</a> is a great place to get started.</a>
						</p>
					</div>
				</div>

				<div class="Card">
					<div class="Card-inner">
						<p class="Card-title">Troubleshooting</p>
						<p class="Card-content">Experiencing problems? Start with our
							<a href="https://github.com/golang/vscode-go/wiki/troubleshooting">troubleshooting guide</a>.  </p> </div>
				</div>
			</div>
			</main>

			<script nonce="${nonce}" src="${scriptURI}"></script>
			</body>
			</html>`;
	}
}

function getNonce() {
	let text = '';
	const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
	for (let i = 0; i < 32; i++) {
		text += possible.charAt(Math.floor(Math.random() * possible.length));
	}
	return text;
}

function joinPath(uri: vscode.Uri, ...pathFragment: string[]): vscode.Uri {
	// Reimplementation of
	// https://github.com/microsoft/vscode/blob/b251bd952b84a3bdf68dad0141c37137dac55d64/src/vs/base/common/uri.ts#L346-L357
	// with Node.JS path. This is a temporary workaround for https://github.com/eclipse-theia/theia/issues/8752.
	if (!uri.path) {
		throw new Error('[UriError]: cannot call joinPaths on URI without path');
	}
	return uri.with({ path: vscode.Uri.file(path.join(uri.fsPath, ...pathFragment)).path });
}

function showGoWelcomePage() {
	// Update this list of versions when there is a new version where we want to
	// show the welcome page on update.
	const showVersions: string[] = ['0.40.0'];
	// TODO(hyangah): use the content hash instead of hard-coded string.
	// https://github.com/golang/vscode-go/issue/1179
	let goExtensionVersion = '0.40.0';
	let goExtensionVersionKey = 'go.extensionVersion';
	if (extensionInfo.isPreview) {
		goExtensionVersion = '0.0.0';
		goExtensionVersionKey = 'go.nightlyExtensionVersion';
	}

	const savedGoExtensionVersion = getFromGlobalState(goExtensionVersionKey, '');

	if (hasNewsForNewVersion(showVersions, goExtensionVersion, savedGoExtensionVersion)) {
		vscode.commands.executeCommand('go.welcome');
	}
	if (goExtensionVersion !== savedGoExtensionVersion) {
		updateGlobalState(goExtensionVersionKey, goExtensionVersion);
	}
}

export function hasNewsForNewVersion(showVersions: string[], newVersion: string, oldVersion: string): boolean {
	if (newVersion === oldVersion) {
		return false;
	}
	const coercedNew = semver.coerce(newVersion);
	const coercedOld = semver.coerce(oldVersion);
	if (!coercedNew || !coercedOld) {
		return true;
	}
	// Both semver.coerce(0.22.0) and semver.coerce(0.22.0-rc.1) will be 0.22.0.
	return semver.gte(coercedNew, coercedOld) && showVersions.includes(coercedNew.toString());
}
