import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { lift, wrapIn } from 'prosemirror-commands';
import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
import { wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
import { numberRange } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { LinkManager } from '../../../util/LinkManager';
import { SelectionManager } from '../../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { EquationBox } from '../EquationBox';
import { FieldViewProps } from '../FieldView';
import { FormattedTextBox } from './FormattedTextBox';
import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';
const { toggleMark } = require('prosemirror-commands');

@observer
export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
    static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
    static get Instance() {
        return RichTextMenu._instance?.menu;
    }
    public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
    private _linkToRef = React.createRef<HTMLInputElement>();

    layoutDoc: Doc | undefined;
    @observable public view?: EditorView = undefined;
    public editorProps: FieldViewProps | undefined;

    public _brushMap: Map<string, Set<Mark>> = new Map();

    @observable private collapsed: boolean = false;
    @observable private _noLinkActive: boolean = false;
    @observable private _boldActive: boolean = false;
    @observable private _italicsActive: boolean = false;
    @observable private _underlineActive: boolean = false;
    @observable private _strikethroughActive: boolean = false;
    @observable private _subscriptActive: boolean = false;
    @observable private _superscriptActive: boolean = false;

    @observable private _activeFontSize: string = '13px';
    @observable private _activeFontFamily: string = '';
    @observable private _activeListType: string = '';
    @observable private _activeAlignment: string = 'left';

    @observable private brushMarks: Set<Mark> = new Set();
    @observable private showBrushDropdown: boolean = false;

    @observable private _activeFontColor: string = 'black';
    @observable private showColorDropdown: boolean = false;

    @observable private _activeHighlightColor: string = 'transparent';
    @observable private showHighlightDropdown: boolean = false;

    @observable private currentLink: string | undefined = '';
    @observable private showLinkDropdown: boolean = false;

    _reaction: IReactionDisposer | undefined;
    constructor(props: AntimodeMenuProps) {
        super(props);
        makeObservable(this);
        RichTextMenu._instance.menu = this;
        this.updateMenu(undefined, undefined, props, this.layoutDoc);
        this._canFade = false;
        this.Pinned = true;
    }

    @computed get noAutoLink() {
        return this._noLinkActive;
    }
    @computed get bold() {
        return this._boldActive;
    }
    @computed get underline() {
        return this._underlineActive;
    }
    @computed get italics() {
        return this._italicsActive;
    }
    @computed get strikeThrough() {
        return this._strikethroughActive;
    }
    @computed get fontColor() {
        return this._activeFontColor;
    }
    @computed get fontHighlight() {
        return this._activeHighlightColor;
    }
    @computed get fontFamily() {
        return this._activeFontFamily;
    }
    @computed get fontSize() {
        return this._activeFontSize;
    }
    @computed get listStyle() {
        return this._activeListType;
    }
    @computed get textAlign() {
        return this._activeAlignment;
    }
    @computed get textVcenter() {
        return BoolCast(this.layoutDoc?._layout_centered);
    }
    _disposer: IReactionDisposer | undefined;
    componentDidMount() {
        this._disposer = reaction(
            () => SelectionManager.Views.slice(),
            views => this.updateMenu(undefined, undefined, undefined, undefined)
        );
    }
    componentWillUnmount() {
        this._disposer?.();
    }

    @action
    public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any, layoutDoc: Doc | undefined) {
        if (this._linkToRef.current?.getBoundingClientRect().width) {
            return;
        }
        this.view = view;
        this.layoutDoc = layoutDoc;
        props && (this.editorProps = props);

        // Don't do anything if the document/selection didn't change
        if (view && view.hasFocus()) {
            if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
        }

        this.setActiveMarkButtons(this.getActiveMarksOnSelection());
        const active = this.getActiveFontStylesOnSelection();
        const activeFamilies = active.activeFamilies;
        const activeSizes = active.activeSizes;
        const activeColors = active.activeColors;
        const activeHighlights = active.activeHighlights;
        const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
        const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);

        this._activeListType = this.getActiveListStyle();
        this._activeAlignment = this.getActiveAlignment();
        this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
        this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
        this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';

        // update link in current selection
        this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle));
    }

    setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
        if (mark) {
            const liFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.list_item);
            const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item);
            const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list);
            const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst);
            const fromRange = numberRange(state.selection.from).reverse();
            const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
            const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
            if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
                const hasMark = node.marks.some(m => m.type === mark.type);
                const otherMarks = node.marks.filter(m => m.type !== mark.type);
                const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
                const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
                dispatch(updateBullets(markup, state.schema));
            } else {
                const state = this.view?.state;
                const tr = this.view?.state.tr;
                if (tr && state) {
                    if (dontToggle) {
                        tr.addMark(state.selection.from, state.selection.to, mark);
                        dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
                    } else {
                        toggleMark(mark.type, mark.attrs)(state, dispatch);
                    }
                }
            }
        }
    };

    // finds font sizes and families in selection
    getActiveAlignment() {
        if (this.view && this.TextView?._props.rootSelected?.()) {
            const path = (this.view.state.selection.$from as any).path;
            for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
                if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
                    return path[i].attrs.align || 'left';
                }
            }
        }
        return 'left';
    }

    // finds font sizes and families in selection
    getActiveListStyle() {
        const state = this.view?.state;
        if (state) {
            const pos = state.selection.$anchor;
            for (let i = 0; i < pos.depth; i++) {
                const node = pos.node(i);
                if (node.type === schema.nodes.ordered_list) {
                    return node.attrs.mapStyle;
                }
            }
        }
        return '';
    }

    // finds font sizes and families in selection
    getActiveFontStylesOnSelection() {
        const activeFamilies = new Set<string>();
        const activeSizes = new Set<string>();
        const activeColors = new Set<string>();
        const activeHighlights = new Set<string>();
        if (this.view && this.TextView?._props.rootSelected?.()) {
            const state = this.view.state;
            const pos = this.view.state.selection.$from;
            var marks: Mark[] = [...(state.storedMarks ?? [])];
            if (state.storedMarks !== null) {
            } else if (state.selection.empty) {
                for (let i = 0; i <= pos.depth; i++) {
                    marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
                }
            } else {
                state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
                    node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
                });
            }
            marks.forEach(m => {
                m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.family);
                m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.color);
                m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
                m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight));
            });
        } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
            SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
        }
        return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) };
    }

    getMarksInSelection(state: EditorState) {
        const found = new Set<Mark>();
        const { from, to } = state.selection as TextSelection;
        state.doc.nodesBetween(from, to, node => node.marks.forEach(m => found.add(m)));
        return found;
    }

    //finds all active marks on selection in given group
    getActiveMarksOnSelection() {
        if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];

        const state = this.view.state;
        var marks: Mark[] = [...(state.storedMarks ?? [])];
        const pos = this.view.state.selection.$from;
        if (state.storedMarks !== null) {
        } else if (state.selection.empty) {
            for (let i = 0; i <= pos.depth; i++) {
                marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
            }
        } else {
            state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
                node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
            });
        }
        const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
        return markGroup.filter(mark_type => {
            const mark = state.schema.mark(mark_type);
            return mark.isInSet(marks);
        });
    }

    @action
    setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
        if (!activeMarks) return;

        this._noLinkActive = false;
        this._boldActive = false;
        this._italicsActive = false;
        this._underlineActive = false;
        this._strikethroughActive = false;
        this._subscriptActive = false;
        this._superscriptActive = false;

        activeMarks.forEach(mark => {
            // prettier-ignore
            switch (mark.name) {
                case 'noAutoLinkAnchor': this._noLinkActive = true; break;
                case 'strong':           this._boldActive = true; break;
                case 'em':               this._italicsActive = true; break;
                case 'underline':        this._underlineActive = true; break;
                case 'strikethrough':    this._strikethroughActive = true; break;
                case 'subscript':        this._subscriptActive = true; break;
                case 'superscript':      this._superscriptActive = true; break;
            }
        });
    }

    elideSelection = (txstate: EditorState | undefined = undefined, visibility = false) => {
        const state = txstate ?? this.view?.state;
        if (!state || state.selection.empty) return false;
        const mark = state.schema.marks.summarize.create();
        const tr = state.tr.addMark(state.tr.selection.from, state.selection.to, mark);
        const text = tr.selection.content();
        const elideNode = state.schema.nodes.summary.create({ visibility, text, textslice: text.toJSON() });
        const summary = tr.replaceSelectionWith(elideNode).removeMark(tr.selection.from - 1, tr.selection.from, mark);
        const expanded = () => {
            const endOfElidableText = summary.selection.to + text.content.size;
            const res = summary.insert(summary.selection.to, text.content).insert(endOfElidableText, state.schema.nodes.paragraph.create({}));
            return res.setSelection(new TextSelection(res.doc.resolve(endOfElidableText + 1)));
        };
        this.view?.dispatch?.(visibility ? expanded() : summary);
        return true;
    };

    toggleNoAutoLinkAnchor = () => {
        if (this.view) {
            const mark = this.view.state.schema.mark(this.view.state.schema.marks.noAutoLinkAnchor);
            this.setMark(mark, this.view.state, this.view.dispatch, false);
            this.TextView.autoLink();
            this.view.focus();
        }
    };
    toggleBold = () => {
        if (this.view) {
            const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong);
            this.setMark(mark, this.view.state, this.view.dispatch, false);
            this.view.focus();
        }
    };

    toggleUnderline = () => {
        if (this.view) {
            const mark = this.view.state.schema.mark(this.view.state.schema.marks.underline);
            this.setMark(mark, this.view.state, this.view.dispatch, false);
            this.view.focus();
        }
    };

    toggleItalics = () => {
        if (this.view) {
            const mark = this.view.state.schema.mark(this.view.state.schema.marks.em);
            this.setMark(mark, this.view.state, this.view.dispatch, false);
            this.view.focus();
        }
    };

    setFontSize = (fontSize: string) => {
        if (this.view) {
            if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
                this.TextView.dataDoc[this.TextView.fieldKey + '_fontSize'] = fontSize;
                this.view.focus();
            } else {
                const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
                this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
                this.view.focus();
            }
        } else if (SelectionManager.Views.length) {
            SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontSize'] = fontSize));
        } else Doc.UserDoc().fontSize = fontSize;
        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
    };

    setFontFamily = (family: string) => {
        if (this.view) {
            const fmark = this.view.state.schema.marks.pFontFamily.create({ family });
            this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
            this.view.focus();
        } else if (SelectionManager.Views.length) {
            SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontFamily'] = family));
        } else Doc.UserDoc().fontFamily = family;
        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
    };

    setHighlight(color: string) {
        if (this.view) {
            const highlightMark = this.view.state.schema.mark(this.view.state.schema.marks.marker, { highlight: color });
            this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true);
            this.view.focus();
        } else Doc.UserDoc()._fontHighlight = color;
        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
    }

    setColor(color: string) {
        if (this.view) {
            const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color });
            this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
            this.view.focus();
        } else if (SelectionManager.Views.length) {
            SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontColor'] = color));
        } else Doc.UserDoc().fontColor = color;
        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
    }

    // TODO: remove doesn't work
    // remove all node type and apply the passed-in one to the selected text
    changeListType = (mapStyle: string) => {
        const active = this.view?.state && RichTextMenu.Instance?.getActiveListStyle();
        const newMapStyle = active === mapStyle ? '' : mapStyle;
        if (!this.view || newMapStyle === '') return;

        let inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
        const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
        if (inList) {
            const tx2 = updateBullets(this.view.state.tr, schema, newMapStyle, this.view.state.doc.resolve(this.view.state.selection.$anchor.before(1) + 1).pos, this.view.state.doc.resolve(this.view.state.selection.$anchor.after(1)).pos);
            marks && tx2.ensureMarks([...marks]);
            marks && tx2.setStoredMarks([...marks]);
            this.view.dispatch(tx2);
        } else
            !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
                const tx3 = updateBullets(tx2, schema, newMapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
                marks && tx3.ensureMarks([...marks]);
                marks && tx3.setStoredMarks([...marks]);
                this.view!.dispatch(tx3);
            });
        this.view.focus();
        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
    };

    insertSummarizer(state: EditorState, dispatch: any) {
        if (state.selection.empty) return false;
        const mark = state.schema.marks.summarize.create();
        const tr = state.tr;
        tr.addMark(state.selection.from, state.selection.to, mark);
        const content = tr.selection.content();
        const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
        dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
        return true;
    }

    vcenterToggle = (view: EditorView, dispatch: any) => {
        this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered);
    };
    align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
        if (this.TextView?._props.rootSelected?.()) {
            var tr = view.state.tr;
            view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
                if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
                    tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align: alignment }, node.marks);
                    return false;
                }
                view.focus();
                return true;
            });
            view.focus();
            dispatch?.(tr);
        }
    };

    insetParagraph(state: EditorState, dispatch: any) {
        var tr = state.tr;
        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
            if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
                const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10;
                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
                return false;
            }
            return true;
        });
        dispatch?.(tr);
        return true;
    }
    outsetParagraph(state: EditorState, dispatch: any) {
        var tr = state.tr;
        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
            if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
                const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10);
                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
                return false;
            }
            return true;
        });
        dispatch?.(tr);
        return true;
    }

    indentParagraph(state: EditorState, dispatch: any) {
        var tr = state.tr;
        const heading = false;
        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
            if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
                const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
                const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25;
                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
                return false;
            }
            return true;
        });
        !heading && dispatch?.(tr);
        return true;
    }

    hangingIndentParagraph(state: EditorState, dispatch: any) {
        var tr = state.tr;
        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
            if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
                const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
                const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10;
                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
                return false;
            }
            return true;
        });
        dispatch?.(tr);
        return true;
    }

    insertBlockquote(state: EditorState, dispatch: any) {
        const path = (state.selection.$from as any).path;
        if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
            lift(state, dispatch);
        } else {
            wrapIn(schema.nodes.blockquote)(state, dispatch);
        }
        return true;
    }

    insertHorizontalRule(state: EditorState, dispatch: any) {
        dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView());
        return true;
    }

    @action toggleBrushDropdown() {
        this.showBrushDropdown = !this.showBrushDropdown;
    }

    // todo: add brushes to brushMap to save with a style name
    onBrushNameKeyPress = (e: React.KeyboardEvent) => {
        if (e.key === 'Enter') {
            RichTextMenu.Instance?.brushMarks && RichTextMenu.Instance?._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
            this._brushNameRef.current!.style.background = 'lightGray';
        }
    };
    _brushNameRef = React.createRef<HTMLInputElement>();

    @action
    clearBrush() {
        RichTextMenu.Instance && (RichTextMenu.Instance.brushMarks = new Set());
    }

    @action
    fillBrush(state: EditorState, dispatch: any) {
        if (!this.view) return;

        if (!Array.from(this.brushMarks.keys()).length) {
            const selected_marks = this.getMarksInSelection(this.view.state);
            if (selected_marks.size >= 0) {
                this.brushMarks = selected_marks;
            }
        } else {
            const { from, to, $from } = this.view.state.selection;
            if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
                if (to - from > 0) {
                    this.view.dispatch(this.view.state.tr.removeMark(from, to));
                    Array.from(this.brushMarks)
                        .filter(m => m.type !== schema.marks.user_mark)
                        .forEach((mark: Mark) => {
                            this.setMark(mark, this.view!.state, this.view!.dispatch);
                        });
                }
            }
        }
    }

    get TextView() {
        return (this.view as any)?.TextView as FormattedTextBox;
    }
    get TextViewFieldKey() {
        return this.TextView?._props.fieldKey;
    }

    @action setActiveHighlight(color: string) {
        this._activeHighlightColor = color;
    }

    @action setCurrentLink(link: string) {
        this.currentLink = link;
    }

    createLinkButton() {
        const self = this;

        function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
            self.TextView?.endUndoTypingBatch();
            UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), 'link change');
        }

        const link = this.currentLink ? this.currentLink : '';

        const button = (
            <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
                <button className="antimodeMenu-button color-preview-button">
                    <FontAwesomeIcon icon="link" size="lg" />
                </button>
            </Tooltip>
        );

        const dropdownContent = (
            <div className="dropdown link-menu">
                <p>Linked to:</p>
                <input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
                <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}>
                    Apply hyperlink
                </button>
                <div className="divider" />
                <button className="remove-button" onPointerDown={e => this.deleteLink()}>
                    Remove link
                </button>
            </div>
        );

        return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
    }

    async getTextLinkTargetTitle() {
        if (!this.view) return;

        const node = this.view.state.selection.$from.nodeAfter;
        const link = node && node.marks.find(m => m.type.name === 'link');
        if (link) {
            const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined;
            if (href) {
                if (href.indexOf(Doc.localServerPath()) === 0) {
                    const linkclicked = href.replace(Doc.localServerPath(), '').split('?')[0];
                    if (linkclicked) {
                        const linkDoc = await DocServer.GetRefField(linkclicked);
                        if (linkDoc instanceof Doc) {
                            const link_anchor_1 = await Cast(linkDoc.link_anchor_1, Doc);
                            const link_anchor_2 = await Cast(linkDoc.link_anchor_2, Doc);
                            const currentDoc = SelectionManager.Docs.lastElement();
                            if (currentDoc && link_anchor_1 && link_anchor_2) {
                                if (Doc.AreProtosEqual(currentDoc, link_anchor_1)) {
                                    return StrCast(link_anchor_2.title);
                                }
                                if (Doc.AreProtosEqual(currentDoc, link_anchor_2)) {
                                    return StrCast(link_anchor_1.title);
                                }
                            }
                        }
                    }
                } else {
                    return href;
                }
            } else {
                return link.attrs.title;
            }
        }
    }

    // TODO: should check for valid URL
    @undoBatch
    makeLinkToURL = (target: string, lcoation: string) => {
        ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
    };

    @undoBatch
    deleteLink = () => {
        if (this.view) {
            const linkAnchor = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
            if (linkAnchor) {
                const allAnchors = linkAnchor.attrs.allAnchors.slice();
                this.TextView.RemoveAnchorFromSelection(allAnchors);
                // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
                allAnchors
                    .filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0)
                    .forEach((aref: any) => {
                        const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0];
                        anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
                    });
            }
        }
    };

    render() {
        return null;
    }
}

interface ButtonDropdownProps {
    view?: EditorView;
    button: JSX.Element;
    dropdownContent: JSX.Element;
    openDropdownOnButton?: boolean;
    link?: boolean;
    pdf?: boolean;
}

@observer
export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps> {
    @observable private showDropdown: boolean = false;
    private ref: HTMLDivElement | null = null;

    constructor(props: any) {
        super(props);
        makeObservable(this);
    }

    componentDidMount() {
        document.addEventListener('pointerdown', this.onBlur);
    }

    componentWillUnmount() {
        document.removeEventListener('pointerdown', this.onBlur);
    }

    @action
    setShowDropdown(show: boolean) {
        this.showDropdown = show;
    }
    @action
    toggleDropdown() {
        this.showDropdown = !this.showDropdown;
    }

    onDropdownClick = (e: React.PointerEvent) => {
        e.preventDefault();
        e.stopPropagation();
        this.toggleDropdown();
    };

    onBlur = (e: PointerEvent) => {
        setTimeout(() => {
            if (this.ref !== null && !this.ref.contains(e.target as Node)) {
                this.setShowDropdown(false);
            }
        }, 0);
    };

    render() {
        return (
            <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
                {!this._props.pdf ? (
                    <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
                        {this._props.button}
                        <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
                            <FontAwesomeIcon icon="caret-down" size="sm" />
                        </div>
                    </div>
                ) : (
                    <>
                        {this._props.button}
                        <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
                            <FontAwesomeIcon icon="caret-down" size="sm" />
                        </button>
                    </>
                )}
                {this.showDropdown ? this._props.dropdownContent : null}
            </div>
        );
    }
}

interface RichTextMenuPluginProps {
    editorProps: any;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
    render() {
        return null;
    }
    update(view: EditorView, lastState: EditorState | undefined) {
        RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc);
    }
}
