"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EditorImpl = void 0;
const view_1 = require("@codemirror/view");
const prosemirror_state_1 = require("prosemirror-state");
const core_1 = require("../core/index.js");
const bundle_1 = require("../i18n/bundle/index.js");
const logger_1 = require("../logger.js");
const markup_1 = require("../markup/index.js");
const autocomplete_1 = require("../markup/codemirror/autocomplete/index.js");
const editor_1 = require("../markup/editor.js");
const utils_1 = require("../utils/index.js");
const MarkupManager_1 = require("./MarkupManager.js");
const dynamicModifiers_1 = require("./config/dynamicModifiers.js");
/** @internal */
class EditorImpl extends utils_1.SafeEventEmitter {
    #logger;
    #markup;
    #editorMode;
    #toolbarVisible;
    #splitModeEnabled;
    #splitMode;
    #renderPreview;
    #wysiwygEditor;
    #markupEditor;
    #markupConfig;
    #escapeConfig;
    #mdOptions;
    #pmTransformers = [];
    #preserveEmptyRows;
    #modifiers;
    #preset;
    #extensions;
    #renderStorage;
    #fileUploadHandler;
    #parseInsertedUrlAsImage;
    #needToSetDimensionsForUploadedImages;
    #enableNewImageSizeCalculation;
    #directiveSyntax;
    #prepareRawMarkup;
    #beforeEditorModeChange;
    #mobile;
    get _wysiwygView() {
        // @ts-expect-error internal typing
        return this.#wysiwygEditor?.view;
    }
    get logger() {
        return this.#logger;
    }
    get currentMode() {
        return this.#editorMode;
    }
    set currentMode(newMode) {
        switch (newMode) {
            case 'markup': {
                this.#editorMode = newMode;
                if (this.#wysiwygEditor) {
                    const markupEditorValue = this.#markupEditor?.getValue();
                    const wysiwygEditorValue = this.#wysiwygEditor.getValue();
                    if (!markupEditorValue ||
                        this.shouldReplaceMarkupEditorValue(markupEditorValue, wysiwygEditorValue)) {
                        this.#markup = wysiwygEditorValue;
                    }
                    else {
                        this.#markup = markupEditorValue;
                    }
                }
                if (this.#markupEditor)
                    this.#markupEditor.replace(this.#markup);
                break;
            }
            case 'wysiwyg': {
                this.#editorMode = newMode;
                if (this.#markupEditor) {
                    const value = this.#markupEditor.getValue();
                    this.#markup = this.#prepareRawMarkup?.(value) ?? value;
                }
                else if (this.#prepareRawMarkup) {
                    this.#markup = this.#prepareRawMarkup(this.#markup);
                }
                if (this.#wysiwygEditor)
                    this.#wysiwygEditor.replace(this.#markup);
                break;
            }
            default:
                throw new Error('Unknown editor mode: ' + newMode);
        }
        setTimeout(() => {
            this.currentEditor.focus();
        }, 30);
    }
    get toolbarVisible() {
        return this.#toolbarVisible;
    }
    get splitModeEnabled() {
        return this.#splitModeEnabled;
    }
    get splitMode() {
        return this.#splitMode;
    }
    get preset() {
        return this.#preset;
    }
    get mdOptions() {
        return this.#mdOptions;
    }
    get directiveSyntax() {
        return this.#directiveSyntax;
    }
    get renderPreview() {
        return this.#renderPreview;
    }
    get currentEditor() {
        const mode = this.currentMode;
        switch (mode) {
            case 'markup':
                return this.markupEditor;
            case 'wysiwyg':
                return this.wysiwygEditor;
            default:
                throw new Error('Unknown editor mode: ' + mode);
        }
    }
    get wysiwygEditor() {
        if (!this.#wysiwygEditor) {
            const mdPreset = this.#preset === 'zero' || this.#preset === 'commonmark' ? this.#preset : 'default';
            this.#wysiwygEditor = new core_1.WysiwygEditor({
                mdPreset,
                logger: this.logger.nested({ mode: 'wysiwyg' }),
                initialContent: this.#markup,
                extensions: this.#extensions,
                pmTransformers: this.#pmTransformers,
                modifiers: this.#modifiers,
                allowHTML: this.#mdOptions.html,
                linkify: this.#mdOptions.linkify,
                linkifyTlds: this.#mdOptions.linkifyTlds,
                escapeConfig: this.#escapeConfig,
                onChange: () => this.emit('rerender-toolbar', null),
                onDocChange: () => this.emit('change', null),
            });
        }
        return this.#wysiwygEditor;
    }
    get markupEditor() {
        if (!this.#markupEditor) {
            this.#markupEditor = new editor_1.Editor((0, markup_1.createCodemirror)({
                doc: this.#markup,
                logger: this.logger.nested({ mode: 'markup' }),
                placeholder: this.#markupConfig.placeholder ?? (0, bundle_1.i18n)('markup_placeholder'),
                onCancel: () => this.emit('cancel', null),
                onSubmit: () => this.emit('submit', null),
                onChange: () => this.emit('rerender-toolbar', null),
                onDocChange: () => this.emit('change', null),
                onScroll: (event) => this.emit('cm-scroll', { event }),
                reactRenderer: this.#renderStorage,
                uploadHandler: this.fileUploadHandler,
                parseInsertedUrlAsImage: this.parseInsertedUrlAsImage,
                needImageDimensions: this.needToSetDimensionsForUploadedImages,
                parseHtmlOnPaste: this.#markupConfig.parseHtmlOnPaste,
                enableNewImageSizeCalculation: this.enableNewImageSizeCalculation,
                extensions: this.#markupConfig.extensions,
                disabledExtensions: this.#markupConfig.disabledExtensions,
                keymaps: this.#markupConfig.keymaps,
                preserveEmptyRows: this.#preserveEmptyRows,
                yfmLangOptions: {
                    languageData: (0, autocomplete_1.getAutocompleteConfig)({
                        preserveEmptyRows: this.#preserveEmptyRows,
                    }).concat(this.#markupConfig?.languageData || []),
                },
                autocompletion: this.#markupConfig.autocompletion,
                directiveSyntax: this.directiveSyntax,
                receiver: this,
                searchPanel: this.#markupConfig.searchPanel,
            }));
        }
        return this.#markupEditor;
    }
    get renderStorage() {
        return this.#renderStorage;
    }
    get fileUploadHandler() {
        return this.#fileUploadHandler;
    }
    get parseInsertedUrlAsImage() {
        return this.#parseInsertedUrlAsImage;
    }
    get needToSetDimensionsForUploadedImages() {
        return this.#needToSetDimensionsForUploadedImages;
    }
    get enableNewImageSizeCalculation() {
        return this.#enableNewImageSizeCalculation;
    }
    get mobile() {
        return this.#mobile;
    }
    constructor(opts) {
        const { logger } = opts;
        super({
            onError: (error) => {
                logger.error(error);
                logger_1.globalLogger.error(error);
            },
        });
        const { md = {}, initial = {}, handlers = {}, experimental = {}, markupConfig = {}, wysiwygConfig = {}, mobile = false, } = opts;
        this.#logger = logger;
        this.#modifiers = experimental.preserveMarkupFormatting
            ? (0, dynamicModifiers_1.createDynamicModifiers)(new MarkupManager_1.MarkupManager(this.logger.nested({ module: 'markup-manager' })))
            : undefined;
        this.#editorMode = initial.mode ?? 'wysiwyg';
        this.#toolbarVisible = initial.toolbarVisible ?? true;
        this.#splitMode = (markupConfig.renderPreview && markupConfig.splitMode) ?? false;
        this.#splitModeEnabled = (this.#splitMode && initial.splitModeEnabled) ?? false;
        this.#renderPreview = markupConfig.renderPreview;
        this.#markup = initial.markup ?? '';
        this.#preset = opts.preset ?? 'full';
        this.#pmTransformers = opts.pmTransformers;
        this.#mdOptions = md;
        this.#extensions = wysiwygConfig.extensions;
        this.#markupConfig = { ...opts.markupConfig };
        this.#renderStorage = opts.renderStorage;
        this.#fileUploadHandler = handlers.uploadFile;
        this.#parseInsertedUrlAsImage = markupConfig.parseInsertedUrlAsImage;
        this.#needToSetDimensionsForUploadedImages = Boolean(experimental.needToSetDimensionsForUploadedImages);
        this.#directiveSyntax = opts.directiveSyntax;
        this.#enableNewImageSizeCalculation = Boolean(experimental.enableNewImageSizeCalculation);
        this.#preserveEmptyRows = experimental.preserveEmptyRows || false;
        this.#prepareRawMarkup = experimental.prepareRawMarkup;
        this.#escapeConfig = wysiwygConfig.escapeConfig;
        this.#beforeEditorModeChange = experimental.beforeEditorModeChange;
        this.#mobile = mobile;
    }
    // ---> implements CodeEditor
    get cm() {
        return this.markupEditor.cm;
    }
    // <--- implements CodeEditor
    // ---> implements ActionStorage
    get actions() {
        return this.wysiwygEditor.actions;
    }
    action(actionName) {
        return this.wysiwygEditor.action(actionName);
    }
    // <--- implements ActionStorage
    destroy() {
        this.#wysiwygEditor?.destroy();
        this.#markupEditor?.codemirror.destroy();
        this.#markupEditor = undefined;
        this.#markupEditor = undefined;
        this.#wysiwygEditor = undefined;
    }
    setEditorMode(mode, opts) {
        this.changeEditorMode({ mode, reason: 'manually', emit: opts?.emit });
    }
    changeEditorMode({ emit = true, ...opts }) {
        if (this.#editorMode === opts.mode)
            return;
        if (this.#beforeEditorModeChange?.({ mode: opts.mode, reason: opts.reason }) === false) {
            return;
        }
        this.logger.event({
            event: 'mode-change',
            prevMode: this.#editorMode,
            nextMode: opts.mode,
            reason: opts.reason,
        });
        this.currentMode = opts.mode;
        this.emit('rerender', null);
        if (emit) {
            this.emit('change-editor-mode', opts);
        }
    }
    changeToolbarVisibility(opts) {
        this.#toolbarVisible = opts.visible;
        this.emit('rerender', null);
        this.emit('change-toolbar-visibility', opts);
    }
    changeSplitModeEnabled(opts) {
        if (this.#splitModeEnabled === opts.splitModeEnabled)
            return;
        this.#splitModeEnabled = opts.splitModeEnabled;
        this.emit('rerender', null);
        this.emit('change-split-mode-enabled', opts);
    }
    focus() {
        return this.currentEditor.focus();
    }
    hasFocus() {
        return this.currentEditor.hasFocus();
    }
    getValue = () => this.currentEditor.getValue();
    isEmpty() {
        return this.currentEditor.isEmpty();
    }
    clear() {
        return this.currentEditor.clear();
    }
    replace(newMarkup) {
        return this.currentEditor.replace(newMarkup);
    }
    prepend(markup) {
        return this.currentEditor.prepend(markup);
    }
    append(markup) {
        return this.currentEditor.append(markup);
    }
    moveCursor(position) {
        if (typeof position === 'object') {
            return this.moveCursorToLine(position.line);
        }
        return this.currentEditor.moveCursor(position);
    }
    moveCursorToLine(/** 0-based line number */ line) {
        const mode = this.currentMode;
        switch (mode) {
            case 'markup': {
                const view = this.markupEditor.cm;
                let cmLine = line + 1; // lines in codemirror is 1-based
                cmLine = Math.max(cmLine, 1);
                cmLine = Math.min(cmLine, view.state.doc.lines);
                const yMargin = getTopOffset(view.dom);
                const anchor = view.state.doc.line(cmLine).from;
                view.dispatch({
                    scrollIntoView: true,
                    selection: { anchor },
                    effects: [
                        view_1.EditorView.scrollIntoView(anchor, { y: 'start', x: 'start', yMargin }),
                    ],
                });
                break;
                // eslint-disable-next-line no-inner-declarations
            }
            case 'wysiwyg': {
                const elem = this.wysiwygEditor.dom.querySelector(`[data-line="${line}"]`);
                if (elem) {
                    const elemTop = elem.getBoundingClientRect().top;
                    const topOffset = getTopOffset(this.wysiwygEditor.dom);
                    window.scrollTo({ top: elemTop + window.scrollY - topOffset });
                    const position = this._wysiwygView.posAtDOM(elem, 0);
                    const { tr } = this._wysiwygView.state;
                    this._wysiwygView.dispatch(tr.setSelection(prosemirror_state_1.TextSelection.create(tr.doc, position)));
                }
                break;
            }
            default:
                throw new Error('Unknown editor mode: ' + mode);
        }
    }
    shouldReplaceMarkupEditorValue(markupValue, wysiwygValue) {
        const serializedEditorMarkup = this.#wysiwygEditor?.serializer.serialize(this.#wysiwygEditor.parser.parse(markupValue), this.#escapeConfig);
        return serializedEditorMarkup?.trim() !== wysiwygValue.trim();
    }
}
exports.EditorImpl = EditorImpl;
function getTopOffset(elem) {
    const TOOLBAR_HEIGHT = 36; //px
    const TOOLBAR_BOTTOM_OFFSET = 8; // px
    const TOOLBAR_TOP_ADDITIONAL_OFFSET = 8; // px
    const TOOLBAR_TOP_OFFSET_VAR = '--g-md-toolbar-sticky-offset';
    const topOffsetValue = window.getComputedStyle(elem).getPropertyValue(TOOLBAR_TOP_OFFSET_VAR);
    const toolbarTopOffset = calculateCSSNumberValue(topOffsetValue) + TOOLBAR_TOP_ADDITIONAL_OFFSET;
    return toolbarTopOffset + TOOLBAR_HEIGHT + TOOLBAR_BOTTOM_OFFSET;
}
function calculateCSSNumberValue(cssValue) {
    const tmp = document.createElement('div');
    tmp.style.position = 'absolute';
    tmp.style.top = '-99999px';
    tmp.style.left = '-99999px';
    tmp.style.width = `calc(${cssValue})`;
    document.body.appendChild(tmp);
    const value = tmp.getBoundingClientRect().width;
    tmp.remove();
    return value;
}
//# sourceMappingURL=Editor.js.map
