import { LoadingSpinner } from '@sourcegraph/react-loading-spinner';
import { upperFirst } from 'lodash';
import * as React from 'react';
import { from as fromPromise, Subscription } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { SaveToolbar } from '../components/SaveToolbar';
import { asError, isErrorLike } from '../util/errors';
/**
 * Converts a Monaco/vscode style Disposable object to a simple function that can be added to a rxjs Subscription
 */
const disposableToFn = (disposable) => () => disposable.dispose();
/** Displays a MonacoSettingsEditor component without loading Monaco in the current Webpack chunk. */
export class DynamicallyImportedMonacoSettingsEditor extends React.PureComponent {
    constructor() {
        super(...arguments);
        this.state = {};
        this.subscriptions = new Subscription();
        this.monaco = null;
        this.onSave = () => {
            const value = this.effectiveValue;
            if (this.props.onSave) {
                this.props.onSave(value);
            }
        };
        this.onChange = (newValue) => {
            this.setState({ value: newValue }, () => {
                if (this.props.onChange) {
                    this.props.onChange(newValue);
                }
                if (this.props.onDirtyChange) {
                    this.props.onDirtyChange(this.isDirty);
                }
            });
        };
        this.discard = () => {
            if (this.state.value === undefined ||
                this.props.value === this.state.value ||
                window.confirm('Discard edits?')) {
                this.setState({ value: undefined });
            }
        };
        this.monacoRef = (monacoValue) => {
            this.monaco = monacoValue;
            // This function can only be called if the editor was loaded so it is okay to cast here
            const monacoSettingsEditor = this.state.monacoSettingsEditorOrError;
            if (this.monaco && monacoSettingsEditor) {
                this.subscriptions.add(disposableToFn(this.monaco.editor.onDidCreateEditor(editor => {
                    this.configEditor = editor;
                })));
                this.subscriptions.add(disposableToFn(this.monaco.editor.onDidCreateModel(model => {
                    if (this.configEditor &&
                        monacoSettingsEditor.isStandaloneCodeEditor(this.configEditor) &&
                        this.props.actions) {
                        for (const { id, label, run } of this.props.actions) {
                            monacoSettingsEditor.addEditorAction(this.configEditor, model, label, id, run);
                        }
                    }
                })));
            }
        };
    }
    componentDidMount() {
        this.subscriptions.add(fromPromise(import('../settings/MonacoSettingsEditor'))
            .pipe(catchError(error => {
            console.error(error);
            return [asError(error)];
        }))
            .subscribe(m => {
            this.setState({ monacoSettingsEditorOrError: m });
        }));
        // Prevent navigation when dirty.
        this.subscriptions.add(this.props.history.block((location, action) => {
            if (action === 'REPLACE') {
                return undefined;
            }
            if (this.props.loading || this.isDirty) {
                return 'Discard changes?';
            }
            return undefined; // allow navigation
        }));
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
    }
    get effectiveValue() {
        return this.state.value === undefined ? this.props.value : this.state.value;
    }
    get isDirty() {
        return this.effectiveValue !== this.props.value;
    }
    render() {
        if (this.state.monacoSettingsEditorOrError === undefined) {
            return React.createElement(LoadingSpinner, { className: "icon-inline" });
        }
        if (isErrorLike(this.state.monacoSettingsEditorOrError)) {
            return (React.createElement("div", { className: "alert alert-danger" },
                "Error loading: ",
                upperFirst(this.state.monacoSettingsEditorOrError.message)));
        }
        const isDirty = this.isDirty;
        const effectiveValue = this.effectiveValue;
        const MonacoSettingsEditor = this.state.monacoSettingsEditorOrError.MonacoSettingsEditor;
        return (React.createElement(React.Fragment, null,
            this.props.actions && (React.createElement("div", { className: "site-admin-configuration-page__action-groups" },
                React.createElement("div", { className: "site-admin-configuration-page__action-groups" },
                    React.createElement("div", { className: "site-admin-configuration-page__action-group-header" }, "Quick configure:"),
                    React.createElement("div", { className: "site-admin-configuration-page__actions" }, this.props.actions.map(({ id, label }) => (React.createElement("button", { key: id, className: "btn btn-secondary btn-sm site-admin-configuration-page__action", 
                        // tslint:disable-next-line:jsx-no-lambda
                        onClick: () => this.runAction(id, this.configEditor) }, label))))))),
            this.props.canEdit && (React.createElement(SaveToolbar, { dirty: isDirty, disabled: this.props.loading || this.props.saving || !isDirty, saving: this.props.saving, onSave: this.onSave, onDiscard: this.discard })),
            React.createElement(MonacoSettingsEditor, Object.assign({}, this.props, { onDidSave: this.onSave, onChange: this.onChange, value: effectiveValue, monacoRef: this.monacoRef }))));
    }
    runAction(id, editor) {
        if (editor) {
            const action = editor.getAction(id);
            action.run().done(() => void 0, (err) => console.error(err));
        }
        else {
            alert('Wait for editor to load before running action.');
        }
    }
}
//# sourceMappingURL=DynamicallyImportedMonacoSettingsEditor.js.map