import classic from 'ember-classic-decorator';
import {action} from '@ember/object';
import {classNameBindings} from '@ember-decorators/component';
import {inject as service} from '@ember/service';
/* global CodeMirror */
import Component from '@ember/component';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import {assign} from '@ember/polyfills';
import {bind, once, scheduleOnce} from '@ember/runloop';
import {task} from 'ember-concurrency';

@classic
@classNameBindings('isFocused:focus')
class CmEditorComponent extends Component {
    @service lazyLoader;

    textareaClass = '';
    isFocused = false;

    // options for the editor
    autofocus = false;

    indentUnit = 4;
    lineNumbers = true;
    lineWrapping = false;
    mode = 'htmlmixed';
    theme = 'xq-light';
    _editor = null; // reference to CodeMirror editor

    // Allowed actions
    'focus-in' = () => {};
    'focus-out' = () => {};

    update = () => {};

    @boundOneWay('value')
        _value; // make sure a value exists

    didReceiveAttrs() {
        super.didReceiveAttrs(...arguments);

        if (this._value === null || undefined) {
            this.set('_value', '');
        }

        if (this.mode !== this._lastMode && this._editor) {
            this._editor.setOption('mode', this.mode);
        }
        this._lastMode = this.mode;
    }

    didInsertElement() {
        super.didInsertElement(...arguments);
        this.initCodeMirror.perform();
    }

    willDestroyElement() {
        super.willDestroyElement(...arguments);

        // Ensure the editor exists before trying to destroy it. This fixes
        // an error that occurs if codemirror hasn't finished loading before
        // the component is destroyed.
        if (this._editor) {
            let editor = this._editor.getWrapperElement();
            editor.parentNode.removeChild(editor);
            this._editor = null;
        }
    }

    @action
    updateFromTextarea(value) {
        this.update(value);
    }

    @task(function* () {
        let loader = this.lazyLoader;
        yield loader.loadScript('codemirror', 'assets/codemirror/codemirror.js');

        scheduleOnce('afterRender', this, this._initCodeMirror);
    })
        initCodeMirror;

    _initCodeMirror() {
        let options = this.getProperties('lineNumbers', 'lineWrapping', 'indentUnit', 'mode', 'theme', 'autofocus');
        assign(options, {value: this._value});

        let textarea = this.element.querySelector('textarea');
        if (textarea && textarea === document.activeElement) {
            options.autofocus = true;
        }

        this._editor = new CodeMirror.fromTextArea(textarea, options);

        // by default CodeMirror will place the cursor at the beginning of the
        // content, it makes more sense for the cursor to be at the end
        if (options.autofocus) {
            this._editor.setCursor(this._editor.lineCount(), 0);
        }

        // events
        this._setupCodeMirrorEventHandler('focus', this, this._focus);
        this._setupCodeMirrorEventHandler('blur', this, this._blur);
        this._setupCodeMirrorEventHandler('change', this, this._update);
    }

    _setupCodeMirrorEventHandler(event, target, method) {
        let callback = bind(target, method);

        this._editor.on(event, callback);

        this.one('willDestroyElement', this, function () {
            this._editor.off(event, callback);
        });
    }

    _update(codeMirror, changeObj) {
        once(this, this.update, codeMirror.getValue(), codeMirror, changeObj);
    }

    _focus(codeMirror, event) {
        this.set('isFocused', true);
        once(this, this['focus-in'], codeMirror.getValue(), codeMirror, event);
    }

    _blur/* codeMirror, event */() {
        this.set('isFocused', false);
        this['focus-out']?.();
    }
}

export default CmEditorComponent;
