/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
import 'mocha';
import * as os from 'os';
import * as vscode from 'vscode';
import { closeAllEditors, delay, disposeAll } from '../utils';

const webviewId = 'myWebview';

function workspaceFile(...segments: string[]) {
	return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments);
}

const testDocument = workspaceFile('bower.json');

suite.skip('vscode API - webview', () => {
	const disposables: vscode.Disposable[] = [];

	function _register<T extends vscode.Disposable>(disposable: T) {
		disposables.push(disposable);
		return disposable;
	}

	teardown(async () => {
		await closeAllEditors();

		disposeAll(disposables);
	});

	test('webviews should be able to send and receive messages', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true }));
		const firstResponse = getMesssage(webview);
		webview.webview.html = createHtmlDocumentWithBody(/*html*/`
			<script>
				const vscode = acquireVsCodeApi();
				window.addEventListener('message', (message) => {
					vscode.postMessage({ value: message.data.value + 1 });
				});
			</script>`);

		webview.webview.postMessage({ value: 1 });
		assert.strictEqual((await firstResponse).value, 2);
	});

	test('webviews should not have scripts enabled by default', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, {}));
		const response = Promise.race<any>([
			getMesssage(webview),
			new Promise<{}>(resolve => setTimeout(() => resolve({ value: '🎉' }), 1000))
		]);
		webview.webview.html = createHtmlDocumentWithBody(/*html*/`
			<script>
				const vscode = acquireVsCodeApi();
				vscode.postMessage({ value: '💉' });
			</script>`);

		assert.strictEqual((await response).value, '🎉');
	});

	test('webviews should update html', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true }));

		{
			const response = getMesssage(webview);
			webview.webview.html = createHtmlDocumentWithBody(/*html*/`
				<script>
					const vscode = acquireVsCodeApi();
					vscode.postMessage({ value: 'first' });
				</script>`);

			assert.strictEqual((await response).value, 'first');
		}
		{
			const response = getMesssage(webview);
			webview.webview.html = createHtmlDocumentWithBody(/*html*/`
				<script>
					const vscode = acquireVsCodeApi();
					vscode.postMessage({ value: 'second' });
				</script>`);

			assert.strictEqual((await response).value, 'second');
		}
	});

	test.skip('webviews should preserve vscode API state when they are hidden', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true }));
		const ready = getMesssage(webview);
		webview.webview.html = createHtmlDocumentWithBody(/*html*/`
			<script>
				const vscode = acquireVsCodeApi();
				let value = (vscode.getState() || {}).value || 0;

				window.addEventListener('message', (message) => {
					switch (message.data.type) {
					case 'get':
						vscode.postMessage({ value });
						break;

					case 'add':
						++value;;
						vscode.setState({ value });
						vscode.postMessage({ value });
						break;
					}
				});

				vscode.postMessage({ type: 'ready' });
			</script>`);
		await ready;

		const firstResponse = await sendRecieveMessage(webview, { type: 'add' });
		assert.strictEqual(firstResponse.value, 1);

		// Swap away from the webview
		const doc = await vscode.workspace.openTextDocument(testDocument);
		await vscode.window.showTextDocument(doc);

		// And then back
		const ready2 = getMesssage(webview);
		webview.reveal(vscode.ViewColumn.One);
		await ready2;

		// We should still have old state
		const secondResponse = await sendRecieveMessage(webview, { type: 'get' });
		assert.strictEqual(secondResponse.value, 1);
	});

	test('webviews should preserve their context when they are moved between view columns', async () => {
		const doc = await vscode.workspace.openTextDocument(testDocument);
		await vscode.window.showTextDocument(doc, vscode.ViewColumn.One);

		// Open webview in same column
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true }));
		const ready = getMesssage(webview);
		webview.webview.html = statefulWebviewHtml;
		await ready;

		const firstResponse = await sendRecieveMessage(webview, { type: 'add' });
		assert.strictEqual(firstResponse.value, 1);

		// Now move webview to new view column
		webview.reveal(vscode.ViewColumn.Two);

		// We should still have old state
		const secondResponse = await sendRecieveMessage(webview, { type: 'get' });
		assert.strictEqual(secondResponse.value, 1);
	});

	test('webviews with retainContextWhenHidden should preserve their context when they are hidden', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
		const ready = getMesssage(webview);

		webview.webview.html = statefulWebviewHtml;
		await ready;

		const firstResponse = await sendRecieveMessage(webview, { type: 'add' });
		assert.strictEqual((await firstResponse).value, 1);

		// Swap away from the webview
		const doc = await vscode.workspace.openTextDocument(testDocument);
		await vscode.window.showTextDocument(doc);

		// And then back
		webview.reveal(vscode.ViewColumn.One);

		// We should still have old state
		const secondResponse = await sendRecieveMessage(webview, { type: 'get' });
		assert.strictEqual(secondResponse.value, 1);
	});

	test('webviews with retainContextWhenHidden should preserve their page position when hidden', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
		const ready = getMesssage(webview);
		webview.webview.html = createHtmlDocumentWithBody(/*html*/`
			${'<h1>Header</h1>'.repeat(200)}
			<script>
				const vscode = acquireVsCodeApi();

				setTimeout(() => {
					window.scroll(0, 100);
					vscode.postMessage({ value: window.scrollY });
				}, 500);

				window.addEventListener('message', (message) => {
					switch (message.data.type) {
						case 'get':
							vscode.postMessage({ value: window.scrollY });
							break;
					}
				});
				vscode.postMessage({ type: 'ready' });
			</script>`);
		await ready;

		const firstResponse = getMesssage(webview);

		assert.strictEqual(Math.round((await firstResponse).value), 100);

		// Swap away from the webview
		const doc = await vscode.workspace.openTextDocument(testDocument);
		await vscode.window.showTextDocument(doc);

		// And then back
		webview.reveal(vscode.ViewColumn.One);

		// We should still have old scroll pos
		const secondResponse = await sendRecieveMessage(webview, { type: 'get' });
		assert.strictEqual(Math.round(secondResponse.value), 100);
	});

	test('webviews with retainContextWhenHidden should be able to recive messages while hidden', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
		const ready = getMesssage(webview);

		webview.webview.html = statefulWebviewHtml;
		await ready;

		const firstResponse = await sendRecieveMessage(webview, { type: 'add' });
		assert.strictEqual((await firstResponse).value, 1);

		// Swap away from the webview
		const doc = await vscode.workspace.openTextDocument(testDocument);
		await vscode.window.showTextDocument(doc);

		// Try posting a message to our hidden webview
		const secondResponse = await sendRecieveMessage(webview, { type: 'add' });
		assert.strictEqual((await secondResponse).value, 2);

		// Now show webview again
		webview.reveal(vscode.ViewColumn.One);

		// We should still have old state
		const thirdResponse = await sendRecieveMessage(webview, { type: 'get' });
		assert.strictEqual(thirdResponse.value, 2);
	});


	test.skip('webviews should only be able to load resources from workspace by default', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', {
			viewColumn: vscode.ViewColumn.One
		}, {
			enableScripts: true
		}));

		webview.webview.html = createHtmlDocumentWithBody(/*html*/`
			<script>
				const vscode = acquireVsCodeApi();
				window.addEventListener('message', (message) => {
					const img = document.createElement('img');
					img.addEventListener('load', () => {
						vscode.postMessage({ value: true });
					});
					img.addEventListener('error', () => {
						vscode.postMessage({ value: false });
					});
					img.src = message.data.src;
					document.body.appendChild(img);
				});

				vscode.postMessage({ type: 'ready' });
			</script>`);

		const ready = getMesssage(webview);
		await ready;

		{
			const imagePath = webview.webview.asWebviewUri(workspaceFile('image.png'));
			const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
			assert.strictEqual(response.value, true);
		}
		// {
		// 	// #102188. Resource filename containing special characters like '%', '#', '?'.
		// 	const imagePath = webview.webview.asWebviewUri(workspaceFile('image%02.png'));
		// 	const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
		// 	assert.strictEqual(response.value, true);
		// }
		// {
		// 	// #102188. Resource filename containing special characters like '%', '#', '?'.
		// 	const imagePath = webview.webview.asWebviewUri(workspaceFile('image%.png'));
		// 	const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
		// 	assert.strictEqual(response.value, true);
		// }
		{
			const imagePath = webview.webview.asWebviewUri(workspaceFile('no-such-image.png'));
			const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
			assert.strictEqual(response.value, false);
		}
		{
			const imagePath = webview.webview.asWebviewUri(workspaceFile('..', '..', '..', 'resources', 'linux', 'code.png'));
			const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
			assert.strictEqual(response.value, false);
		}
	});

	test.skip('webviews should allow overriding allowed resource paths using localResourceRoots', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, {
			enableScripts: true,
			localResourceRoots: [workspaceFile('sub')]
		}));

		webview.webview.html = createHtmlDocumentWithBody(/*html*/`
			<script>
				const vscode = acquireVsCodeApi();
				window.addEventListener('message', (message) => {
					const img = document.createElement('img');
					img.addEventListener('load', () => { vscode.postMessage({ value: true }); });
					img.addEventListener('error', () => { vscode.postMessage({ value: false }); });
					img.src = message.data.src;
					document.body.appendChild(img);
				});
			</script>`);

		{
			const response = sendRecieveMessage(webview, { src: webview.webview.asWebviewUri(workspaceFile('sub', 'image.png')).toString() });
			assert.strictEqual((await response).value, true);
		}
		{
			const response = sendRecieveMessage(webview, { src: webview.webview.asWebviewUri(workspaceFile('image.png')).toString() });
			assert.strictEqual((await response).value, false);
		}
	});

	test.skip('webviews using hard-coded old style vscode-resource uri should work', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, {
			enableScripts: true,
			localResourceRoots: [workspaceFile('sub')]
		}));

		const imagePath = workspaceFile('sub', 'image.png').with({ scheme: 'vscode-resource' }).toString();

		webview.webview.html = createHtmlDocumentWithBody(/*html*/`
			<img src="${imagePath}">
			<script>
				const vscode = acquireVsCodeApi();
				const img = document.getElementsByTagName('img')[0];
				img.addEventListener('load', () => { vscode.postMessage({ value: true }); });
				img.addEventListener('error', () => { vscode.postMessage({ value: false }); });
			</script>`);

		const firstResponse = getMesssage(webview);

		assert.strictEqual((await firstResponse).value, true);
	});

	test('webviews should have real view column after they are created, #56097', async () => {
		const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.Active }, { enableScripts: true }));

		// Since we used a symbolic column, we don't know what view column the webview will actually show in at first
		assert.strictEqual(webview.viewColumn, undefined);

		let changed = false;
		const viewStateChanged = new Promise<vscode.WebviewPanelOnDidChangeViewStateEvent>((resolve) => {
			webview.onDidChangeViewState(e => {
				if (changed) {
					throw new Error('Only expected a single view state change');
				}
				changed = true;
				resolve(e);
			}, undefined, disposables);
		});

		assert.strictEqual((await viewStateChanged).webviewPanel.viewColumn, vscode.ViewColumn.One);

		const firstResponse = getMesssage(webview);
		webview.webview.html = createHtmlDocumentWithBody(/*html*/`
			<script>
				const vscode = acquireVsCodeApi();
				vscode.postMessage({  });
			</script>`);

		webview.webview.postMessage({ value: 1 });
		await firstResponse;
		assert.strictEqual(webview.viewColumn, vscode.ViewColumn.One);
	});

	if (os.platform() === 'darwin') {
		test.skip('webview can copy text from webview', async () => {
			const expectedText = `webview text from: ${Date.now()}!`;

			const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
			const ready = getMesssage(webview);


			webview.webview.html = createHtmlDocumentWithBody(/*html*/`
			<b>${expectedText}</b>
			<script>
				const vscode = acquireVsCodeApi();
				document.execCommand('selectAll');
				vscode.postMessage({ type: 'ready' });
			</script>`);
			await ready;

			await vscode.commands.executeCommand('editor.action.clipboardCopyAction');
			await delay(200); // Make sure copy has time to reach webview
			assert.strictEqual(await vscode.env.clipboard.readText(), expectedText);
		});
	}
});

function createHtmlDocumentWithBody(body: string): string {
	return /*html*/`<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>Document</title>
</head>
<body>
	${body}
</body>
</html>`;
}

const statefulWebviewHtml = createHtmlDocumentWithBody(/*html*/ `
	<script>
		const vscode = acquireVsCodeApi();
		let value = 0;
		window.addEventListener('message', (message) => {
			switch (message.data.type) {
				case 'get':
					vscode.postMessage({ value });
					break;

				case 'add':
					++value;;
					vscode.setState({ value });
					vscode.postMessage({ value });
					break;
			}
		});
		vscode.postMessage({ type: 'ready' });
	</script>`);


function getMesssage<R = any>(webview: vscode.WebviewPanel): Promise<R> {
	return new Promise<R>(resolve => {
		const sub = webview.webview.onDidReceiveMessage(message => {
			sub.dispose();
			resolve(message);
		});
	});
}

function sendRecieveMessage<T = {}, R = any>(webview: vscode.WebviewPanel, message: T): Promise<R> {
	const p = getMesssage<R>(webview);
	webview.webview.postMessage(message);
	return p;
}
