/*
 * Copyright 2020 The Kubernetes Authors
 *
 * 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.
 */
import React from 'react';
import { extname } from 'path';
import { editor as Monaco } from 'monaco-editor';
import { Events } from '@kui-shell/core';
import { isFile } from '@kui-shell/plugin-bash-like/fs';
import getKuiFontSize from './lib/fonts';
import { language } from './lib/file-types';
import defaultMonacoOptions from './lib/defaults';
import '../../../../web/scss/components/Editor/Editor.scss';
export default class DiffEditor extends React.PureComponent {
    constructor(props) {
        super(props);
        // created below in render() via ref={...} -> initMonaco()
        this.state = {
            cleaners: [],
            editor: undefined,
            wrapper: undefined,
            catastrophicError: undefined
        };
    }
    static getDerivedStateFromError(error) {
        return { catastrophicError: error };
    }
    componentDidCatch(error, errorInfo) {
        console.error('catastrophic error in Editor', error, errorInfo);
    }
    /** 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 DiffEditor.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() {
        this.state.cleaners.forEach(cleaner => cleaner());
    }
    /** Called when we have a ready wrapper (monaco's init requires an wrapper */
    static initMonaco(props, state) {
        const cleaners = [];
        try {
            // here we instantiate an editor widget
            const providedOptions = {
                readOnly: true,
                renderSideBySide: props.renderSideBySide
            };
            const overrides = { theme: props.light ? 'vs' : 'vs-dark' };
            const options = Object.assign(defaultMonacoOptions(providedOptions), providedOptions, overrides);
            const contentType = props.contentType === 'text/plain'
                ? language(props.contentType, isFile(props.response) ? extname(props.response.spec.filepath).slice(1) : undefined)
                : props.contentType || undefined;
            const editor = Monaco.createDiffEditor(state.wrapper, options);
            editor.setModel({
                original: Monaco.createModel(props.originalContent, contentType),
                modified: Monaco.createModel(props.modifiedContent, contentType)
            });
            editor.onDidUpdateDiff(() => {
                const lineChanges = editor.getLineChanges();
                if (lineChanges && lineChanges.length !== 0) {
                    editor.revealLineInCenterIfOutsideViewport(lineChanges[0].originalStartLineNumber);
                }
            });
            const onZoom = () => {
                editor.updateOptions({ fontSize: getKuiFontSize() });
            };
            Events.eventChannelUnsafe.on('/zoom', onZoom);
            cleaners.push(() => Events.eventChannelUnsafe.off('/zoom', onZoom));
            if (props.sizeToFit) {
                const sizeToFit = (_width, height = Math.min(0.4 * window.innerHeight, Math.max(250, editor.getModifiedEditor().getContentHeight()))) => {
                    // if we know 1) the height of the content won't change, and
                    // 2) we are running in "simple" mode (this is mostly the case
                    // for inline editor components, as opposed to editor
                    // components that are intended to fill the full view), then:
                    // size the height to fit the content
                    state.wrapper.style.flexBasis = height + 'px';
                };
                sizeToFit();
                const observer = new ResizeObserver(entries => {
                    sizeToFit(entries[0].contentRect.width, entries[0].contentRect.height);
                    editor.layout();
                });
                observer.observe(state.wrapper);
                cleaners.push(() => observer.disconnect());
                const onTabLayoutChange = (evt) => {
                    sizeToFit();
                    if (evt.isWidthConstrained) {
                        editor.updateOptions({ renderSideBySide: false });
                    }
                    else {
                        editor.updateOptions({ renderSideBySide: props.renderSideBySide });
                    }
                };
                Events.eventBus.onTabLayoutChange(props.tabUUID, onTabLayoutChange);
                cleaners.push(() => Events.eventBus.offTabLayoutChange(props.tabUUID, onTabLayoutChange));
            }
            else {
                const onTabLayoutChange = (evt) => {
                    editor.layout();
                    if (evt.isWidthConstrained) {
                        editor.updateOptions({ renderSideBySide: false });
                    }
                    else {
                        editor.updateOptions({ renderSideBySide: props.renderSideBySide });
                    }
                };
                Events.eventBus.onTabLayoutChange(props.tabUUID, onTabLayoutChange);
                cleaners.push(() => Events.eventBus.offTabLayoutChange(props.tabUUID, onTabLayoutChange));
            }
            state.wrapper['getValueForTests'] = () => {
                return editor.getModifiedEditor().getValue();
            };
            cleaners.push(() => {
                const model = editor.getModel();
                if (model) {
                    model.original.dispose();
                    model.modified.dispose();
                }
            });
            return {
                editor,
                cleaners
            };
        }
        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=DiffEditor.js.map