// *****************************************************************************
// Copyright (C) 2018 Ericsson and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { injectable, inject, optional } from '@theia/core/shared/inversify';
import { MenuModelRegistry, CommandRegistry, nls, PreferenceScope, PreferenceService } from '@theia/core';
import {
    CommonMenus,
    AbstractViewContribution,
    CommonCommands,
    KeybindingRegistry,
    Widget,
    QuickInputService,
    QuickPickItem,
    isFirefox,
} from '@theia/core/lib/browser';
import { isOSX } from '@theia/core/lib/common/os';
import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { EditorManager, EditorWidget } from '@theia/editor/lib/browser';
import URI from '@theia/core/lib/common/uri';
import { PreferencesWidget } from './views/preference-widget';
import { Preference, PreferencesCommands, PreferenceMenus } from './util/preference-types';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { WorkspaceService } from '@theia/workspace/lib/browser';
import { FileStat } from '@theia/filesystem/lib/common/files';

@injectable()
export class PreferencesContribution extends AbstractViewContribution<PreferencesWidget> {

    @inject(FileService) protected readonly fileService: FileService;
    @inject(EditorManager) protected readonly editorManager: EditorManager;
    @inject(PreferenceService) protected readonly preferenceService: PreferenceService;
    @inject(ClipboardService) protected readonly clipboardService: ClipboardService;
    @inject(PreferencesWidget) protected readonly scopeTracker: PreferencesWidget;
    @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService;
    @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService;

    constructor() {
        super({
            widgetId: PreferencesWidget.ID,
            widgetName: PreferencesWidget.LABEL,
            defaultWidgetOptions: {
                area: 'main',
            },
        });
    }

    override registerCommands(commands: CommandRegistry): void {
        commands.registerCommand(CommonCommands.OPEN_PREFERENCES, {
            execute: async (query?: string) => {
                const widget = await this.openView({ activate: true });
                if (typeof query === 'string') {
                    widget.setSearchTerm(query);
                }
            },
        });
        commands.registerCommand(PreferencesCommands.OPEN_PREFERENCES_JSON_TOOLBAR, {
            isEnabled: () => true,
            isVisible: w => this.withWidget(w, () => true),
            execute: (preferenceId: string) => {
                this.openPreferencesJSON(preferenceId);
            }
        });
        commands.registerCommand(PreferencesCommands.COPY_JSON_NAME, {
            isEnabled: Preference.EditorCommandArgs.is,
            isVisible: Preference.EditorCommandArgs.is,
            execute: ({ id, value }: Preference.EditorCommandArgs) => {
                this.clipboardService.writeText(id);
            }
        });
        commands.registerCommand(PreferencesCommands.COPY_JSON_VALUE, {
            isEnabled: Preference.EditorCommandArgs.is,
            isVisible: Preference.EditorCommandArgs.is,
            execute: ({ id, value }: { id: string, value: string; }) => {
                const jsonString = `"${id}": ${JSON.stringify(value)}`;
                this.clipboardService.writeText(jsonString);
            }
        });
        commands.registerCommand(PreferencesCommands.RESET_PREFERENCE, {
            isEnabled: Preference.EditorCommandArgs.is,
            isVisible: Preference.EditorCommandArgs.is,
            execute: ({ id }: Preference.EditorCommandArgs) => {
                this.preferenceService.set(id, undefined, Number(this.scopeTracker.currentScope.scope), this.scopeTracker.currentScope.uri);
            }
        });
        commands.registerCommand(PreferencesCommands.OPEN_USER_PREFERENCES, {
            execute: async () => {
                const widget = await this.openView({ activate: true });
                widget.setScope(PreferenceScope.User);
            }
        });
        commands.registerCommand(PreferencesCommands.OPEN_WORKSPACE_PREFERENCES, {
            isEnabled: () => !!this.workspaceService.workspace,
            isVisible: () => !!this.workspaceService.workspace,
            execute: async () => {
                const widget = await this.openView({ activate: true });
                widget.setScope(PreferenceScope.Workspace);
            }
        });
        commands.registerCommand(PreferencesCommands.OPEN_FOLDER_PREFERENCES, {
            isEnabled: () => !!this.workspaceService.isMultiRootWorkspaceOpened && this.workspaceService.tryGetRoots().length > 0,
            isVisible: () => !!this.workspaceService.isMultiRootWorkspaceOpened && this.workspaceService.tryGetRoots().length > 0,
            execute: () => this.openFolderPreferences(root => {
                this.openView({ activate: true });
                this.scopeTracker.setScope(root.resource);
            })
        });
        commands.registerCommand(PreferencesCommands.OPEN_USER_PREFERENCES_JSON, {
            execute: async () => this.openJson(PreferenceScope.User)
        });
        commands.registerCommand(PreferencesCommands.OPEN_WORKSPACE_PREFERENCES_JSON, {
            isEnabled: () => !!this.workspaceService.workspace,
            isVisible: () => !!this.workspaceService.workspace,
            execute: async () => this.openJson(PreferenceScope.Workspace)
        });
        commands.registerCommand(PreferencesCommands.OPEN_FOLDER_PREFERENCES_JSON, {
            isEnabled: () => !!this.workspaceService.isMultiRootWorkspaceOpened && this.workspaceService.tryGetRoots().length > 0,
            isVisible: () => !!this.workspaceService.isMultiRootWorkspaceOpened && this.workspaceService.tryGetRoots().length > 0,
            execute: () => this.openFolderPreferences(root => this.openJson(PreferenceScope.Folder, root.resource.toString()))
        });
    }

    override registerMenus(menus: MenuModelRegistry): void {
        menus.registerMenuAction(CommonMenus.FILE_SETTINGS_SUBMENU_OPEN, {
            commandId: CommonCommands.OPEN_PREFERENCES.id,
            label: nls.localizeByDefault('Settings'),
            order: 'a10',
        });
        menus.registerMenuAction(CommonMenus.MANAGE_SETTINGS, {
            commandId: CommonCommands.OPEN_PREFERENCES.id,
            label: nls.localizeByDefault('Settings'),
            order: 'a10',
        });
        menus.registerMenuAction(PreferenceMenus.PREFERENCE_EDITOR_CONTEXT_MENU, {
            commandId: PreferencesCommands.RESET_PREFERENCE.id,
            label: PreferencesCommands.RESET_PREFERENCE.label,
            order: 'a'
        });
        menus.registerMenuAction(PreferenceMenus.PREFERENCE_EDITOR_COPY_ACTIONS, {
            commandId: PreferencesCommands.COPY_JSON_VALUE.id,
            label: PreferencesCommands.COPY_JSON_VALUE.label,
            order: 'b'
        });
        menus.registerMenuAction(PreferenceMenus.PREFERENCE_EDITOR_COPY_ACTIONS, {
            commandId: PreferencesCommands.COPY_JSON_NAME.id,
            label: PreferencesCommands.COPY_JSON_NAME.label,
            order: 'c'
        });
    }

    override registerKeybindings(keybindings: KeybindingRegistry): void {
        keybindings.registerKeybinding({
            command: CommonCommands.OPEN_PREFERENCES.id,
            keybinding: (isOSX && !isFirefox) ? 'cmd+,' : 'ctrl+,'
        });
    }

    registerToolbarItems(toolbar: TabBarToolbarRegistry): void {
        toolbar.registerItem({
            id: PreferencesCommands.OPEN_PREFERENCES_JSON_TOOLBAR.id,
            command: PreferencesCommands.OPEN_PREFERENCES_JSON_TOOLBAR.id,
            tooltip: PreferencesCommands.OPEN_USER_PREFERENCES_JSON.label,
            priority: 0,
        });
    }

    protected async openPreferencesJSON(opener: string | PreferencesWidget): Promise<void> {
        const { scope, activeScopeIsFolder, uri } = this.scopeTracker.currentScope;
        const scopeID = Number(scope);
        let preferenceId = '';
        if (typeof opener === 'string') {
            preferenceId = opener;
            const currentPreferenceValue = this.preferenceService.inspect(preferenceId, uri);
            const valueInCurrentScope = Preference.getValueInScope(currentPreferenceValue, scopeID) ?? currentPreferenceValue?.defaultValue;
            this.preferenceService.set(preferenceId, valueInCurrentScope, scopeID, uri);
        }

        let jsonEditorWidget: EditorWidget;
        const jsonUriToOpen = await this.obtainConfigUri(scopeID, activeScopeIsFolder, uri);
        if (jsonUriToOpen) {
            jsonEditorWidget = await this.editorManager.open(jsonUriToOpen);

            if (preferenceId) {
                const text = jsonEditorWidget.editor.document.getText();
                if (preferenceId) {
                    const { index } = text.match(preferenceId)!;
                    const numReturns = text.slice(0, index).match(new RegExp('\n', 'g'))!.length;
                    jsonEditorWidget.editor.cursor = { line: numReturns, character: 4 + preferenceId.length + 4 };
                }
            }
        }
    }

    protected async openJson(scope: PreferenceScope, resource?: string): Promise<void> {
        const jsonUriToOpen = await this.obtainConfigUri(scope, false, resource);
        if (jsonUriToOpen) {
            await this.editorManager.open(jsonUriToOpen);
        }
    }

    /**
     * Prompts which workspace root folder to open the JSON settings.
     */
    protected async openFolderPreferences(callback: (root: FileStat) => unknown): Promise<void> {
        const roots = this.workspaceService.tryGetRoots();
        if (roots.length === 1) {
            callback(roots[0]);
        } else {
            const items: QuickPickItem[] = roots.map(root => ({
                label: root.name,
                description: root.resource.path.fsPath(),
                execute: () => callback(root)
            }));
            this.quickInputService?.showQuickPick(items, { placeholder: 'Select workspace folder' });
        }
    }

    private async obtainConfigUri(serializedScope: number, activeScopeIsFolder: boolean, resource?: string): Promise<URI | undefined> {
        let scope: PreferenceScope = serializedScope;
        if (activeScopeIsFolder) {
            scope = PreferenceScope.Folder;
        }
        const resourceUri = !!resource ? resource : undefined;
        const configUri = this.preferenceService.getConfigUri(scope, resourceUri);
        if (!configUri) {
            return undefined;
        }
        if (configUri && !await this.fileService.exists(configUri)) {
            await this.fileService.create(configUri);
        }
        return configUri;
    }

    /**
     * Determine if the current widget is the PreferencesWidget.
     */
    protected withWidget<T>(widget: Widget | undefined = this.tryGetWidget(), fn: (widget: PreferencesWidget) => T): T | false {
        if (widget instanceof PreferencesWidget && widget.id === PreferencesWidget.ID) {
            return fn(widget);
        }
        return false;
    }
}
