/*
 * Copyright 2020 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import * as React from 'react';
import { extname } from 'path';
import { editor as Monaco, Range } from 'monaco-editor';
import { isFile } from '@kui-shell/plugin-bash-like/fs';
import { i18n } from '@kui-shell/core';
import ClearButton from './ClearButton';
import SaveFileButton from './SaveFileButton';
import RevertFileButton from './RevertFileButton';
import { language } from './lib/file-types';
import defaultMonacoOptions from './lib/defaults';
import '../../../../web/scss/components/Editor/Editor.scss';
const strings = i18n('plugin-client-common', 'editor');
export default class Editor extends React.PureComponent {
    constructor(props) {
        super(props);
        // created below in render() via ref={...} -> initMonaco()
        this.state = {
            editor: undefined,
            wrapper: undefined,
            catastrophicError: undefined
        };
    }
    static getDerivedStateFromError(error) {
        return { catastrophicError: error };
    }
    componentDidCatch(error, errorInfo) {
        console.error('catastrophic error in Editor', error, errorInfo);
    }
    /**
     * ToolbarText for a clean editor, i.e. no changes have been made
     * since last save.
     *
     */
    static allClean(props) {
        return {
            type: 'info',
            text: strings(!props.readOnly ? 'isUpToDate' : 'isUpToDateReadonly')
        };
    }
    /**
     * ToolbarText to indicate error saving.
     *
     */
    static error(props, msg, ...args) {
        return {
            type: 'error',
            text: strings(msg, ...args)
        };
    }
    /** Called whenever we have proposed (props,state); we derive a new State */
    static getDerivedStateFromProps(props, state) {
        if (!state.editor && state.wrapper) {
            // then we are ready to render monaco into the wrapper
            return Editor.initMonaco(props, state);
        }
        else {
            return state;
        }
    }
    /** Called when this component is no longer attached to the document */
    componentWillUnmount() {
        this.destroyMonaco();
    }
    /** Called when we no longer need the monaco-editor instance */
    destroyMonaco() {
        if (this.state.editor) {
            this.state.editor.dispose();
            const model = this.state.editor.getModel();
            if (model) {
                model.dispose();
            }
            if (this.state.subscription) {
                this.state.subscription.dispose();
            }
        }
    }
    static isClearable(props) {
        return ((isFile(props.response) && !props.readOnly) ||
            (!isFile(props.response) && props.content.spec && props.content.spec.clearable !== false));
    }
    static onChange(props, editor) {
        let currentDecorations;
        return () => {
            if (currentDecorations) {
                editor.deltaDecorations(currentDecorations, []);
                currentDecorations = undefined;
            }
            const clearable = Editor.isClearable(props);
            const buttons = [];
            // save
            if (isFile(props.response)) {
                const save = SaveFileButton(editor, props.repl, props.response, (success) => {
                    if (success) {
                        props.willUpdateToolbar(this.allClean(props), !clearable ? undefined : [ClearButton(editor)]);
                    }
                    else {
                        props.willUpdateToolbar(this.error(props, 'errorSaving'));
                    }
                });
                buttons.push(save);
            }
            else if (props.content.spec && props.content.spec.save) {
                const { onSave } = props.content.spec.save;
                buttons.push({
                    mode: 'Save',
                    label: props.content.spec.save.label || strings('saveLocalFile'),
                    kind: 'view',
                    command: () => __awaiter(this, void 0, void 0, function* () {
                        try {
                            const save = yield onSave(editor.getValue());
                            if (!(save && save.noToolbarUpdate)) {
                                props.willUpdateToolbar((save && save.toolbarText) || this.allClean(props), !clearable ? undefined : [ClearButton(editor)]);
                            }
                        }
                        catch (error) {
                            const err = error;
                            if (err.revealLine) {
                                editor.revealLineInCenter(err.revealLine);
                                currentDecorations = editor.deltaDecorations(currentDecorations || [], [
                                    {
                                        range: new Range(err.revealLine, 1, err.revealLine, 1),
                                        options: {
                                            isWholeLine: true,
                                            className: 'kui--editor-line-highlight',
                                            glyphMarginClassName: 'kui--editor-line-glyph'
                                        }
                                    }
                                ]);
                            }
                            const alert = {
                                type: 'warning',
                                text: strings('isModified'),
                                alerts: [
                                    {
                                        type: 'error',
                                        title: strings('errorApplying'),
                                        body: err.message
                                    }
                                ]
                            };
                            props.willUpdateToolbar(alert, buttons, undefined);
                        }
                    })
                });
            }
            // revert
            if (isFile(props.response)) {
                const revert = RevertFileButton(editor, props.repl, props.response, (success, data) => {
                    if (success) {
                        editor.setValue(data);
                        props.willUpdateToolbar(this.allClean(props), !clearable ? undefined : [ClearButton(editor)]);
                    }
                    else {
                        props.willUpdateToolbar(this.error(props, 'errorReverting'));
                    }
                });
                buttons.push(revert);
            }
            else if (props.content.spec && props.content.spec.revert) {
                const { onRevert } = props.content.spec.revert;
                buttons.push({
                    mode: 'Revert',
                    label: props.content.spec.revert.label || strings('revert'),
                    kind: 'view',
                    command: () => __awaiter(this, void 0, void 0, function* () {
                        try {
                            const data = yield onRevert();
                            editor.setValue(data);
                            props.willUpdateToolbar(this.allClean(props), !clearable ? undefined : [ClearButton(editor)]);
                        }
                        catch (err) {
                            console.error(err);
                            props.willUpdateToolbar(this.error(props, 'errorReverting'));
                        }
                    })
                });
            }
            // clear
            if (clearable) {
                buttons.push(ClearButton(editor));
            }
            props.willUpdateToolbar({
                type: 'warning',
                text: strings('isModified')
            }, buttons);
        };
    }
    /** Handle Toolbar registrations */
    static subscribeToChanges(props, editor) {
        if (props.willUpdateToolbar) {
            // send an initial update; note how the initial toolbarText may
            // be governed by the response
            const msg = props.response.toolbarText || this.allClean(props);
            const buttons = props.response.toolbarText ? [] : !Editor.isClearable(props) ? undefined : [ClearButton(editor)];
            props.willUpdateToolbar(msg, buttons);
            // then subscribe to future model change events
            return editor.onDidChangeModelContent(Editor.onChange(props, editor));
        }
    }
    /** Called when we have a ready wrapper (monaco's init requires an wrapper */
    static initMonaco(props, state) {
        try {
            // here we instantiate an editor widget
            const providedOptions = {
                value: props.content.content,
                readOnly: !isFile(props.response) &&
                    (!props.content.spec || props.content.spec.readOnly !== false) &&
                    (props.readOnly || !isFile(props.response) || false),
                language: props.content.contentType === 'text/plain'
                    ? language(props.content.contentType, isFile(props.response) ? extname(props.response.spec.filepath).slice(1) : undefined)
                    : props.content.contentType || undefined
            };
            const options = Object.assign(defaultMonacoOptions(providedOptions), providedOptions);
            const editor = Monaco.create(state.wrapper, options);
            editor['clearDecorations'] = () => {
                // debug('clearing decorations', editor['__cloudshell_decorations'])
                const none = [{ range: new Range(1, 1, 1, 1), options: {} }];
                editor['__cloudshell_decorations'] = editor.deltaDecorations(editor['__cloudshell_decorations'] || [], none);
            };
            state.wrapper['getValueForTests'] = () => {
                return editor.getValue();
            };
            if (!props.readOnly) {
                state.wrapper.focus();
            }
            return {
                editor,
                subscription: Editor.subscribeToChanges(props, editor)
            };
        }
        catch (err) {
            console.error('Error initing Monaco: ', err);
            state.catastrophicError = err;
            return state;
        }
    }
    render() {
        if (this.state.catastrophicError) {
            return React.createElement("div", { className: "oops" },
                " ",
                this.state.catastrophicError.toString());
        }
        else {
            return (React.createElement("div", { className: "code-highlighting" },
                React.createElement("div", { className: "monaco-editor-wrapper", ref: wrapper => this.setState({ wrapper }) })));
        }
    }
}
//# sourceMappingURL=index.js.map