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

import 'vs/css!./media/serverGroupDialog';

import { Colorbox } from 'sql/base/browser/ui/colorbox/colorbox';
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Event, Emitter } from 'vs/base/common/event';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';

import { Button } from 'sql/base/browser/ui/button/button';
import { HideReason, Modal } from 'sql/workbench/browser/modal/modal';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { ServerGroupViewModel } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { ILogService } from 'vs/platform/log/common/log';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { attachModalDialogStyler } from 'sql/workbench/common/styler';
import { assertIsDefined, isUndefinedOrNull } from 'vs/base/common/types';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
import { RequiredIndicatorClassName } from 'sql/base/browser/ui/label/label';
import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles';

interface IRenderedServerGroupDialog {
	groupNameInputBox: InputBox;
	groupDescriptionInputBox: InputBox;
	serverGroupContainer: HTMLElement;
	addServerButton: Button;
	closeButton: Button;
}

export class ServerGroupDialog extends Modal {
	private _colorColorBoxesMap: Array<{ color: string, colorbox: Colorbox }> = [];
	private _selectedColorOption?: number;
	private _viewModel?: ServerGroupViewModel;
	private _skipGroupNameValidation: boolean = false;

	private _onAddServerGroup = new Emitter<void>();
	public onAddServerGroup: Event<void> = this._onAddServerGroup.event;

	private _onCancel = new Emitter<void>();
	public onCancel: Event<void> = this._onCancel.event;

	private _onCloseEvent = new Emitter<void>();
	public onCloseEvent: Event<void> = this._onCloseEvent.event;

	// rendered elements
	private isRendered = false;
	private _groupNameInputBox?: InputBox;
	private _groupDescriptionInputBox?: InputBox;
	private _serverGroupContainer?: HTMLElement;
	private _addServerButton?: Button;
	private _closeButton?: Button;

	constructor(
		@ILayoutService layoutService: ILayoutService,
		@IThemeService themeService: IThemeService,
		@IContextViewService private _contextViewService: IContextViewService,
		@IAdsTelemetryService telemetryService: IAdsTelemetryService,
		@IContextKeyService contextKeyService: IContextKeyService,
		@IClipboardService clipboardService: IClipboardService,
		@ILogService logService: ILogService,
		@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
	) {
		super(localize('ServerGroupsDialogTitle', "Server Groups"), TelemetryKeys.ModalDialogName.ServerGroups, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService);
	}

	public override render() {
		super.render();
		attachModalDialogStyler(this, this._themeService);
		const okLabel = localize('serverGroup.ok', "OK");
		const cancelLabel = localize('serverGroup.cancel', "Cancel");
		this._addServerButton = this.addFooterButton(okLabel, () => this.addGroup());
		this._closeButton = this.addFooterButton(cancelLabel, () => this.cancel(), 'right', true);
		this.isRendered = true;
		this.registerListeners();
	}

	protected layout(height?: number): void {
		// NO OP
	}

	protected renderBody(container: HTMLElement) {
		const body = DOM.append(container, DOM.$('.server-group-dialog'));

		// Connection Group Name
		const serverGroupNameLabel = localize('connectionGroupName', "Server group name");

		DOM.append(body, DOM.$(`.dialog-label.${RequiredIndicatorClassName}`)).innerText = serverGroupNameLabel;

		this._groupNameInputBox = new InputBox(DOM.append(body, DOM.$('.input-divider')), this._contextViewService, {
			validationOptions: {
				validation: (value: string) => !value && !this._skipGroupNameValidation ? ({ type: MessageType.ERROR, content: localize('MissingGroupNameError', "Group name is required.") }) : null
			},
			ariaLabel: serverGroupNameLabel,
			required: true,
			inputBoxStyles: defaultInputBoxStyles
		});

		// Connection Group Description
		const groupDescriptionLabel = localize('groupDescription', "Group description");
		DOM.append(body, DOM.$('.dialog-label')).innerText = groupDescriptionLabel;

		this._groupDescriptionInputBox = new InputBox(DOM.append(body, DOM.$('.input-divider')), this._contextViewService, {
			ariaLabel: groupDescriptionLabel,
			inputBoxStyles: defaultInputBoxStyles
		});

		// Connection Group Color
		const groupColorLabel = localize('groupColor', "Group color");
		DOM.append(body, DOM.$('.dialog-label')).innerText = groupColorLabel;

		this._serverGroupContainer = DOM.append(body, DOM.$('.group-color-options'));
		this._serverGroupContainer.setAttribute('role', 'radiogroup');
		this._serverGroupContainer.setAttribute('aria-label', groupColorLabel);
		this.fillGroupColors(this._serverGroupContainer);

		DOM.addStandardDisposableListener(this._serverGroupContainer, DOM.EventType.KEY_DOWN, (event: StandardKeyboardEvent) => {
			if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.LeftArrow)) {
				this.preventDefaultKeyboardEvent(event);
				this.focusNextColor(event.equals(KeyCode.RightArrow));
			}
		});
	}

	private preventDefaultKeyboardEvent(e: StandardKeyboardEvent) {
		e.preventDefault();
		e.stopPropagation();
	}

	private focusNextColor(moveRight: boolean): void {
		let focusIndex: number = -1;
		for (let i = 0; i < this._colorColorBoxesMap.length; i++) {
			if (document.activeElement === this._colorColorBoxesMap[i].colorbox.radioButton) {
				focusIndex = i;
				break;
			}
		}

		if (focusIndex >= 0) {
			if (moveRight) {
				focusIndex++;
			}
			else {
				focusIndex--;
			}

			// check for wraps
			if (focusIndex < 0) {
				focusIndex = this._colorColorBoxesMap.length - 1;
			} else if (focusIndex >= this._colorColorBoxesMap.length) {
				focusIndex = 0;
			}

			this._colorColorBoxesMap[focusIndex].colorbox.focus();
		}
	}

	private onSelectGroupColor(colorToSelect: string): void {
		this.withViewModel.groupColor = colorToSelect;
		this._selectedColorOption = this.withViewModel.colors.indexOf(colorToSelect);
		this.updateView();
	}

	private registerListeners(): void {
		const renderedDialog = this.withRenderedDialog;
		this._register(renderedDialog.addServerButton);
		this._register(renderedDialog.closeButton);

		// handler for name change events
		this._register(renderedDialog.groupNameInputBox.onDidChange(groupName => {
			this.groupNameChanged(groupName);
		}));

		// handler for description change events
		this._register(renderedDialog.groupDescriptionInputBox.onDidChange(groupDescription => {
			this.groupDescriptionChanged(groupDescription);
		}));
	}

	private fillGroupColors(container: HTMLElement): void {
		for (let i = 0; i < this.withViewModel.colors.length; i++) {
			const color = this.withViewModel.colors[i];

			const colorBox = this._register(new Colorbox(container, {
				name: 'server-group-color',
				color: color
			}));
			this._register(colorBox.onSelect((viaKeyboard) => {
				this.onSelectGroupColor(color);
			}));

			// add the new colorbox to the color map
			this._colorColorBoxesMap[i] = { color, colorbox: colorBox };
		}
	}

	private groupNameChanged(groupName: string) {
		this.withViewModel.groupName = groupName;
		this.updateView();
	}

	private groupDescriptionChanged(groupDescription: string) {
		this.withViewModel.groupDescription = groupDescription;
		this.updateView();
	}

	public get groupName(): string {
		return this.withRenderedDialog.groupNameInputBox.value;
	}

	public get groupDescription(): string {
		return this.withRenderedDialog.groupDescriptionInputBox.value;
	}

	public get selectedColor(): string | undefined {
		return !isUndefinedOrNull(this._selectedColorOption) ? this._colorColorBoxesMap[this._selectedColorOption].color : undefined;
	}

	public get viewModel(): ServerGroupViewModel | undefined {
		return this._viewModel;
	}

	public setViewModel(theViewModel: ServerGroupViewModel) {
		this._viewModel = theViewModel;
		if (this._serverGroupContainer) {
			DOM.clearNode(this._serverGroupContainer);
			this.fillGroupColors(this._serverGroupContainer);
		}
	}

	public addGroup(): void {
		if (this.withRenderedDialog.addServerButton.enabled) {
			if (this.validateInputs()) {
				this._onAddServerGroup.fire();
			}
		}
	}

	public hideError() {
		this.setError('');
	}

	private validateInputs(): boolean {
		const renderedDialog = this.withRenderedDialog;
		const isNameValid = renderedDialog.groupNameInputBox.validate() === undefined;
		if (!isNameValid) {
			renderedDialog.groupNameInputBox.focus();
		}
		return isNameValid;
	}

	// initialize the view based on the current state of the view model
	private initializeView(): void {
		const renderedDialog = this.withRenderedDialog;
		this.title = this.withViewModel.getDialogTitle();

		this._skipGroupNameValidation = true;
		renderedDialog.groupNameInputBox.value = this.withViewModel.groupName;
		this._skipGroupNameValidation = false;

		renderedDialog.groupDescriptionInputBox.value = this.withViewModel.groupDescription ?? '';

		this.updateView();
	}

	// update UI elements that have derivative behaviors based on other state changes
	private updateView(): void {
		// check the color buttons and if their checked state does not match the view model state then correct it
		for (let i = 0; i < this._colorColorBoxesMap.length; i++) {
			let { colorbox: colorbox, color } = this._colorColorBoxesMap[i];
			if ((this.withViewModel.groupColor === color) && (colorbox.checked === false)) {
				colorbox.checked = true;
				this._selectedColorOption = i;
			} else if ((this.withViewModel.groupColor !== color) && (colorbox.checked === true)) {
				colorbox.checked = false;
			}
		}
		// OK button state - enabled if there are pending changes that can be saved
		this.withRenderedDialog.addServerButton.enabled = this.withViewModel.hasPendingChanges();
	}

	private get withViewModel(): ServerGroupViewModel {
		return assertIsDefined(this._viewModel);
	}

	/* Overwrite escape key behavior */
	protected override onClose() {
		this.cancel();
	}

	/* Overwrite enter key behavior */
	protected override onAccept() {
		this.addGroup();
	}

	public cancel() {
		this._onCancel.fire();
		this.close('cancel');
	}

	public close(hideReason: HideReason = 'close') {
		this.hide(hideReason);
		this.withRenderedDialog.groupNameInputBox.hideMessage();
		this._onCloseEvent.fire();
	}

	private get withRenderedDialog(): IRenderedServerGroupDialog {
		if (this.isRendered) {
			// we assume if we are rendered then all our rendered elements are defined
			return {
				groupNameInputBox: this._groupNameInputBox!,
				groupDescriptionInputBox: this._groupDescriptionInputBox!,
				serverGroupContainer: this._serverGroupContainer!,
				addServerButton: this._addServerButton!,
				closeButton: this._closeButton!
			};
		} else {
			throw new Error('Server Group Dialog Not Rendered');
		}
	}

	public open() {
		// reset the dialog
		this.hideError();
		this.initializeView();
		this.show();
		this.withRenderedDialog.groupNameInputBox.focus();
	}
}
