import * as jsonc from '@sqs/jsonc-parser';
import * as monaco from 'monaco-editor';
import * as React from 'react';
import { Subject, Subscription } from 'rxjs';
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
import contributionSchema from 'sourcegraph/lib/protocol/contribution.schema.json';
import { MonacoEditor } from '../components/MonacoEditor';
import jsonSchemaMetaSchema from '../schema/json-schema.schema.json';
import settingsSchema from '../schema/settings.schema.json';
import { eventLogger } from '../tracking/eventLogger';
const isLightThemeToMonacoTheme = (isLightTheme) => (isLightTheme ? 'vs' : 'sourcegraph-dark');
/**
 * A JSON settings editor using the Monaco editor.
 */
export class MonacoSettingsEditor extends React.PureComponent {
    constructor() {
        super(...arguments);
        this.state = {};
        this.monaco = null;
        this.editor = null;
        this.componentUpdates = new Subject();
        this.subscriptions = new Subscription();
        this.disposables = [];
        this.editorWillMount = (e) => {
            this.monaco = e;
            if (e) {
                this.onDidEditorMount();
            }
        };
    }
    componentDidMount() {
        const componentUpdates = this.componentUpdates.pipe(startWith(this.props));
        this.subscriptions.add(componentUpdates
            .pipe(map(props => props.readOnly), distinctUntilChanged())
            .subscribe(readOnly => {
            if (this.editor) {
                this.editor.updateOptions({ readOnly });
            }
        }));
        this.subscriptions.add(componentUpdates
            .pipe(map(props => props.isLightTheme), distinctUntilChanged(), map(isLightThemeToMonacoTheme))
            .subscribe(monacoTheme => {
            if (this.monaco) {
                this.monaco.editor.setTheme(monacoTheme);
            }
        }));
    }
    componentWillReceiveProps(newProps) {
        this.componentUpdates.next(newProps);
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
        for (const disposable of this.disposables) {
            disposable.dispose();
        }
        this.monaco = null;
        this.editor = null;
    }
    render() {
        return (React.createElement(MonacoEditor, { id: this.props.id, language: "json", height: this.props.height || 400, theme: isLightThemeToMonacoTheme(this.props.isLightTheme), value: this.props.value, editorWillMount: this.editorWillMount, options: {
                lineNumbers: 'off',
                automaticLayout: true,
                minimap: { enabled: false },
                formatOnType: true,
                formatOnPaste: true,
                autoIndent: true,
                renderIndentGuides: false,
                glyphMargin: false,
                folding: false,
                renderLineHighlight: 'none',
                scrollBeyondLastLine: false,
                quickSuggestions: true,
                quickSuggestionsDelay: 200,
                readOnly: this.props.readOnly,
                wordWrap: 'on',
            } }));
    }
    onDidEditorMount() {
        const monaco = this.monaco;
        if (this.props.monacoRef) {
            this.props.monacoRef(monaco);
            this.subscriptions.add(() => {
                if (this.props.monacoRef) {
                    this.props.monacoRef(null);
                }
            });
        }
        monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
            validate: true,
            allowComments: true,
            schemas: [
                {
                    uri: this.props.jsonSchema.$id,
                    schema: this.props.jsonSchema,
                    fileMatch: ['*'],
                },
                // Include these schemas because they are referenced by other schemas.
                {
                    uri: 'http://json-schema.org/draft-07/schema',
                    schema: jsonSchemaMetaSchema,
                },
                {
                    uri: 'https://sourcegraph.com/v1/settings.schema.json#',
                    schema: settingsSchema,
                },
                {
                    // This is the literal relative URI used in extension.schema.json to refer to the contributions
                    // JSON Schema.
                    uri: './contribution.schema.json',
                    schema: contributionSchema,
                },
                {
                    // This is the absolute URI of the contributions JSON Schema.
                    uri: 'https://sourcegraph.com/v1/contribution.schema.json#',
                    schema: contributionSchema,
                },
            ],
        });
        monaco.editor.defineTheme('sourcegraph-dark', {
            base: 'vs-dark',
            inherit: true,
            colors: {
                'editor.background': '#0E121B',
                'editor.foreground': '#F2F4F8',
                'editorCursor.foreground': '#A2B0CD',
                'editor.selectionBackground': '#1C7CD650',
                'editor.selectionHighlightBackground': '#1C7CD625',
                'editor.inactiveSelectionBackground': '#1C7CD625',
            },
            rules: [],
        });
        this.disposables.push(monaco.editor.onDidCreateEditor(editor => this.onDidCreateEditor(editor)));
        this.disposables.push(monaco.editor.onDidCreateModel(model => this.onDidCreateModel(model)));
    }
    onDidCreateEditor(editor) {
        this.editor = editor;
        // Necessary to wrap in setTimeout or else _standaloneKeyBindingService won't be ready and the editor will
        // refuse to add the command because it's missing the keybinding service.
        setTimeout(() => {
            if (isStandaloneCodeEditor(editor)) {
                editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, () => {
                    if (this.props.onDidSave) {
                        this.props.onDidSave();
                    }
                }, '');
            }
        });
    }
    onDidCreateModel(model) {
        this.disposables.push(model.onDidChangeContent(() => {
            if (this.props.onChange) {
                this.props.onChange(model.getValue());
            }
        }));
    }
}
export function isStandaloneCodeEditor(editor) {
    return editor.getEditorType() === monaco.editor.EditorType.ICodeEditor;
}
export function toMonacoEdits(model, edits) {
    return edits.map((edit, i) => ({
        identifier: { major: model.getVersionId(), minor: i },
        range: monaco.Range.fromPositions(model.getPositionAt(edit.offset), model.getPositionAt(edit.offset + edit.length)),
        forceMoveMarkers: true,
        text: edit.content,
    }));
}
export function addEditorAction(inputEditor, model, label, id, run) {
    inputEditor.addAction({
        label,
        id,
        run: editor => {
            eventLogger.log('SiteConfigurationActionExecuted', { id });
            editor.focus();
            editor.pushUndoStop();
            const { edits, selectText, cursorOffset } = run(editor.getValue());
            const monacoEdits = toMonacoEdits(model, edits);
            let selection;
            if (typeof selectText === 'string') {
                const afterText = jsonc.applyEdits(editor.getValue(), edits);
                let offset = afterText.slice(edits[0].offset).indexOf(selectText);
                if (offset !== -1) {
                    offset += edits[0].offset;
                    if (typeof cursorOffset === 'number') {
                        selection = monaco.Selection.fromPositions(getPositionAt(afterText, offset + cursorOffset), getPositionAt(afterText, offset + cursorOffset));
                    }
                    else {
                        selection = monaco.Selection.fromPositions(getPositionAt(afterText, offset), getPositionAt(afterText, offset + selectText.length));
                    }
                }
            }
            if (!selection) {
                selection = monaco.Selection.fromPositions(monacoEdits[0].range.getStartPosition(), monacoEdits[monacoEdits.length - 1].range.getEndPosition());
            }
            editor.executeEdits(id, monacoEdits, [selection]);
            editor.revealPositionInCenter(selection.getStartPosition());
        },
    });
}
function getPositionAt(text, offset) {
    const lines = text.split('\n');
    let pos = 0;
    for (const [i, line] of lines.entries()) {
        if (offset < pos + line.length + 1) {
            return new monaco.Position(i + 1, offset - pos + 1);
        }
        pos += line.length + 1;
    }
    throw new Error(`offset ${offset} out of bounds in text of length ${text.length}`);
}
//# sourceMappingURL=MonacoSettingsEditor.js.map