import { LoadingSpinner } from '@sourcegraph/react-loading-spinner';
import { upperFirst } from 'lodash';
import * as React from 'react';
import { from as fromPromise, Subject, Subscription } from 'rxjs';
import { catchError, distinctUntilChanged, filter, map, startWith } from 'rxjs/operators';
import { SaveToolbar } from '../components/SaveToolbar';
import { settingsActions } from '../site-admin/configHelpers';
import { eventLogger } from '../tracking/eventLogger';
import { asError, isErrorLike } from '../util/errors';
const emptySettings = '{\n  // add settings here (Ctrl+Space to see hints)\n}';
const disposableToFn = (disposable) => () => disposable.dispose();
export class SettingsFile extends React.PureComponent {
    constructor(props) {
        super(props);
        this.componentUpdates = new Subject();
        this.subscriptions = new Subscription();
        this.monaco = null;
        this.monacoRef = (monacoValue) => {
            this.monaco = monacoValue;
            // This function can only be called if the editor was loaded correctly so casting is correct here.
            const monacoSettingsEditor = this.state.monacoSettingsEditorOrError;
            if (this.monaco && monacoSettingsEditor) {
                this.subscriptions.add(disposableToFn(this.monaco.editor.onDidCreateEditor(editor => {
                    this.editor = editor;
                })));
                this.subscriptions.add(disposableToFn(this.monaco.editor.onDidCreateModel(model => {
                    if (this.editor && monacoSettingsEditor.isStandaloneCodeEditor(this.editor)) {
                        for (const { id, label, run } of settingsActions) {
                            monacoSettingsEditor.addEditorAction(this.editor, model, label, id, run);
                        }
                    }
                })));
            }
        };
        this.discard = () => {
            if (this.getPropsSettingsContentsOrEmpty() === this.state.contents ||
                window.confirm('Discard settings edits?')) {
                eventLogger.log('SettingsFileDiscard');
                this.setState({
                    contents: undefined,
                    editingLastID: undefined,
                });
                this.props.onDidDiscard();
            }
            else {
                eventLogger.log('SettingsFileDiscardCanceled');
            }
        };
        this.onEditorChange = (newValue) => {
            if (newValue !== this.getPropsSettingsContentsOrEmpty()) {
                this.setState({ editingLastID: this.getPropsSettingsID() });
            }
            this.setState({ contents: newValue });
        };
        this.save = () => {
            eventLogger.log('SettingsFileSaved');
            this.setState({ saving: true }, () => {
                this.props.onDidCommit(this.getPropsSettingsID(), this.state.contents);
            });
        };
        this.state = { saving: false };
        // Reset state upon navigation to a different subject.
        this.componentUpdates
            .pipe(startWith(props), map(({ settings }) => settings), distinctUntilChanged())
            .subscribe(settings => {
            if (this.state.contents !== undefined) {
                this.setState({ contents: undefined });
            }
        });
        // Saving ended (in failure) if we get a commitError.
        this.subscriptions.add(this.componentUpdates
            .pipe(map(({ commitError }) => commitError), distinctUntilChanged(), filter(commitError => !!commitError))
            .subscribe(() => this.setState({ saving: false })));
        // We are finished saving when we receive the new settings ID and it's
        // higher than the one we saved on top of.
        const refreshedAfterSave = this.componentUpdates.pipe(filter(({ settings }) => !!settings), distinctUntilChanged((a, b) => (!a.settings && !!b.settings) ||
            (!!a.settings && !b.settings) ||
            (!!a.settings &&
                !!b.settings &&
                a.settings.configuration.contents === b.settings.configuration.contents &&
                a.settings.id === b.settings.id)), filter(({ settings, commitError }) => !!settings &&
            !commitError &&
            ((typeof this.state.editingLastID === 'number' && settings.id > this.state.editingLastID) ||
                (typeof settings.id === 'number' && this.state.editingLastID === null))));
        this.subscriptions.add(refreshedAfterSave.subscribe(({ settings }) => this.setState({
            saving: false,
            editingLastID: undefined,
            contents: settings ? settings.configuration.contents : undefined,
        })));
    }
    componentDidMount() {
        this.subscriptions.add(fromPromise(import('./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.state.saving || this.dirty) {
                return 'Discard settings changes?';
            }
            return undefined; // allow navigation
        }));
    }
    componentWillReceiveProps(newProps) {
        this.componentUpdates.next(newProps);
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
    }
    get dirty() {
        return this.state.contents !== undefined && this.state.contents !== this.getPropsSettingsContentsOrEmpty();
    }
    render() {
        const dirty = this.dirty;
        const contents = this.state.contents === undefined ? this.getPropsSettingsContentsOrEmpty() : this.state.contents;
        return this.state.monacoSettingsEditorOrError === undefined ? (React.createElement(LoadingSpinner, { className: "icon-inline" })) : isErrorLike(this.state.monacoSettingsEditorOrError) ? (React.createElement("div", { className: "alert alert-danger" },
            "Error loading settings editor: ",
            upperFirst(this.state.monacoSettingsEditorOrError.message))) : ((() => {
            const MonacoSettingsEditor = this.state.monacoSettingsEditorOrError.MonacoSettingsEditor;
            return (React.createElement("div", { className: "settings-file d-flex flex-column" },
                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" }, settingsActions.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) }, label)))))),
                React.createElement(SaveToolbar, { dirty: dirty, disabled: this.state.saving || !dirty, error: this.props.commitError, saving: this.state.saving, onSave: this.save, onDiscard: this.discard }),
                React.createElement(MonacoSettingsEditor, { value: contents, jsonSchema: this.props.jsonSchema, onChange: this.onEditorChange, readOnly: this.state.saving, monacoRef: this.monacoRef, isLightTheme: this.props.isLightTheme, onDidSave: this.save })));
        })());
    }
    runAction(id) {
        if (this.editor) {
            const action = this.editor.getAction(id);
            action.run().done(() => void 0, (err) => console.error(err));
        }
        else {
            alert('Wait for editor to load before running action.');
        }
    }
    getPropsSettingsContentsOrEmpty(settings = this.props.settings) {
        return settings ? settings.configuration.contents : emptySettings;
    }
    getPropsSettingsID() {
        return this.props.settings ? this.props.settings.id : null;
    }
}
//# sourceMappingURL=SettingsFile.js.map