import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { lift, toggleMark, wrapIn } from 'prosemirror-commands';
import { Mark, MarkType } from 'prosemirror-model';
import { wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection, Transaction } 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 { DocServer } from '../../../DocServer';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DocumentView } from '../DocumentView';
import { EquationBox } from '../EquationBox';
import { FieldViewProps } from '../FieldView';
import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox';
import { updateBullets } from './ProsemirrorExampleTransfer';
import './RichTextMenu.scss';
import { schema } from './schema_rts';

@observer
export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
    // eslint-disable-next-line no-use-before-define
    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>();

    dataDoc: Doc | undefined;
    @observable public view?: EditorView & { TextView?: FormattedTextBox } = undefined;
    public editorProps: FieldViewProps | AntimodeMenuProps | 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 _italicActive: 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 _activeFitBox: boolean = false;
    @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;

    constructor(props: AntimodeMenuProps) {
        super(props);
        makeObservable(this);
        runInAction(() => {
            RichTextMenu._instance.menu = this;
            this.updateMenu(undefined, undefined, props, this.dataDoc);
            this._canFade = false;
            this.Pinned = true;
        });
    }

    @computed get RootSelected() {
        return this.TextView?._props.rootSelected?.() || this.TextView?._props.isContentActive();
    }

    @computed get noAutoLink() {
        return this._noLinkActive;
    }
    @computed get bold() {
        return this._boldActive;
    }
    @computed get underline() {
        return this._underlineActive;
    }
    @computed get italic() {
        return this._italicActive;
    }
    @computed get strikeThrough() {
        return this._strikethroughActive;
    }
    @computed get fontColor() {
        return this._activeFontColor;
    }
    @computed get fontHighlight() {
        return this._activeHighlightColor;
    }
    @computed get fitBox() {
        return this._activeFitBox;
    }
    @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.dataDoc?.text_centered, BoolCast(Doc.UserDoc().textCentered));
    }

    @action
    public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: FormattedTextBoxProps | AntimodeMenuProps | undefined, dataDoc: Doc | undefined) {
        if (this._linkToRef.current?.getBoundingClientRect().width) {
            return;
        }
        this.view = view;
        this.dataDoc = dataDoc;
        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;
        const { activeSizes } = active;
        const { activeColors } = active;
        const { activeHighlights } = active;
        const refDoc = DocumentView.Selected().lastElement()?.dataDoc ?? Doc.UserDoc();
        const refField = (pfx => (pfx ? pfx + '_' : ''))(DocumentView.Selected().lastElement()?.LayoutFieldKey);
        const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt));

        this._activeListType = this.getActiveListStyle();
        this._activeAlignment = this.getActiveAlignment();
        this._activeFitBox = BoolCast(refDoc[refField + 'fitBox'], BoolCast(Doc.UserDoc().fitBox));
        this._activeFontFamily = !activeFamilies.length
            ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(this.dataDoc?.[Doc.LayoutFieldKey(this.dataDoc) + '_fontFamily'], refVal('fontFamily', 'Arial')))
            : activeFamilies.length === 1
              ? String(activeFamilies[0])
              : 'various';
        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(this.dataDoc?.[Doc.LayoutFieldKey(this.dataDoc) + '_fontSize'], refVal('fontSize', '10px'))) : activeSizes[0];
        this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('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: (tr: Transaction) => void, dontToggle: boolean = false) => {
        if (mark) {
            const newPos = state.selection.$anchor.node()?.type === 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 if (state) {
                const { 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);
                }
            }
        }
        this.setActiveMarkButtons(this.getActiveMarksOnSelection());
    };

    // finds font sizes and families in selection
    getActiveAlignment = () => {
        if (this.view && this.RootSelected) {
            const from = this.view.state.selection.$from;
            for (let i = from.depth; i >= 0; i--) {
                const node = from.node(i);
                if (node.type === this.view.state.schema.nodes.paragraph || node.type === this.view.state.schema.nodes.heading) {
                    return node.attrs.align || 'left';
                }
            }
        } else if (this.dataDoc) {
            return StrCast(this.dataDoc.text_align) || 'left';
        }
        return StrCast(Doc.UserDoc().textAlign) || '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.RootSelected) {
            const { state } = this.view;
            const pos = this.view.state.selection.$from;
            let marks: Mark[] = [...(state.storedMarks ?? [])];
            if (state.storedMarks !== null) {
                /* empty */
            } 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.fontFamily);
                m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor);
                m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
                m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight));
            });
        } else if (DocumentView.Selected().some(dv => dv.ComponentView instanceof EquationBox)) {
            DocumentView.Selected().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.RootSelected) return [] as MarkType[];

        const { state } = this.view;
        let marks: Mark[] = [...(state.storedMarks ?? [])];
        const pos = this.view.state.selection.$from;
        if (state.storedMarks !== null) {
            /* empty */
        } 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(markType => {
            const mark = state.schema.mark(markType);
            return mark.isInSet(marks);
        });
    }

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

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

        activeMarks.forEach(mark => {
            switch (mark.name) {
                case 'noAutoLinkAnchor': this._noLinkActive = true; break;
                case 'strong':           this._boldActive = true; break;
                case 'em':               this._italicActive = 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;
                default:
            } // prettier-ignore
        });
    }

    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();
        }
    };
    toggleFitBox = () => {
        if (this.dataDoc) {
            const doc = this.dataDoc;
            (document.activeElement as HTMLElement)?.blur();
            doc.text_fitBox = !doc.text_fitBox;
        } else {
            Doc.UserDoc().fitBox = !Doc.UserDoc().fitBox;
            Doc.UserDoc().textAlign = Doc.UserDoc().fitBox ? 'center' : undefined;
        }
        this.updateMenu(undefined, undefined, undefined, this.dataDoc);
    };
    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();
        }
    };

    toggleItalic = () => {
        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();
        }
    };

    setFontField = (value: string, fontField: 'fitBox' | 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
        if (this.TextView && this.view && fontField !== 'fitBox') {
            const anchorNode = window.getSelection()?.anchorNode;
            if (this.view.hasFocus() || (anchorNode && this.TextView.ProseRef?.contains(anchorNode))) {
                const attrs: { [key: string]: string } = {};
                attrs[fontField] = value;
                const fmark = this.view.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
                this.setMark(fmark, this.view.state, (tx: Transaction) => this.view?.dispatch(tx.addStoredMark(fmark)), true);
            } else {
                Array.from(new Set([...DocumentView.Selected(), this.TextView.DocumentView?.()]))
                    .filter(v => v?.ComponentView instanceof FormattedTextBox && v.ComponentView.EditorView?.TextView)
                    .map(v => v!.ComponentView as FormattedTextBox)
                    .forEach(view => {
                        view.EditorView!.TextView!.dataDoc[(view.EditorView!.TextView!.fieldKey ?? 'text') + `_${fontField}`] = value;
                    });
            }
        } else if (this.dataDoc) {
            this.dataDoc[`${Doc.LayoutFieldKey(this.dataDoc)}_${fontField}`] = value;
            this.updateMenu(undefined, undefined, undefined, this.dataDoc);
        } else {
            Doc.UserDoc()[fontField] = value;
            this.updateMenu(undefined, undefined, undefined, this.dataDoc);
        }
    };

    // 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;

        const 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: Transaction) => {
                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();
    };

    insertSummarizer(state: EditorState, dispatch: (tr: Transaction) => void) {
        if (state.selection.empty) return false;
        const mark = state.schema.marks.summarize.create();
        const { tr } = state;
        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 = () => {
        if (this.dataDoc) this.dataDoc.text_centered = !this.dataDoc.text_centered;
        else Doc.UserDoc().textCentered = !Doc.UserDoc().textCentered;
    };
    align = (view: EditorView | undefined, dispatch: undefined | ((tr: Transaction) => void), alignment: 'left' | 'right' | 'center') => {
        if (view && dispatch && this.RootSelected) {
            let { tr } = view.state;
            view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => {
                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);
        } else {
            if (this.dataDoc) {
                this.dataDoc.text_align = alignment;
            } else Doc.UserDoc().textAlign = alignment;
            this.updateMenu(undefined, undefined, undefined, this.dataDoc);
        }
    };

    paragraphSetup(state: EditorState, dispatch: (tr: Transaction) => void, field: 'inset' | 'indent', value?: 0 | 10 | -10) {
        let { tr } = state;
        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
            if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
                const newValue = !value ?  
                    (node.attrs[field] ? 0 : node.attrs[field] + 10) : 
                    Math.max(0, value); // prettier-ignore
                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, ...(field === 'inset' ? { inset: newValue } : { indent: newValue }) }, node.marks);
                return false;
            }
            return true;
        });
        dispatch?.(tr);
        return true;
    }

    insertBlockquote(state: EditorState, dispatch: (tr: Transaction) => void) {
        const node = state.selection.$from.depth ? state.selection.$from.node(state.selection.$from.depth - 1) : undefined;
        if (node?.type === schema.nodes.blockquote) {
            lift(state, dispatch);
        } else {
            wrapIn(schema.nodes.blockquote)(state, dispatch);
        }
        return true;
    }

    insertHorizontalRule(state: EditorState, dispatch: (tr: Transaction) => void) {
        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() {
        if (!this.view) return;

        if (!Array.from(this.brushMarks.keys()).length) {
            const selectedMarks = this.getMarksInSelection(this.view.state);
            if (selectedMarks.size >= 0) {
                this.brushMarks = selectedMarks;
            }
        } 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?.TextView;
    }
    get TextViewFieldKey() {
        return this.TextView?._props.fieldKey;
    }

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

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

    createLinkButton() {
        const onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => {
            this.TextView?.endUndoTypingBatch();
            UndoManager.RunInBatch(() => this.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 type="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 type="button" className="make-button" onPointerDown={() => this.makeLinkToURL(link)}>
                    Apply hyperlink
                </button>
                <div className="divider" />
                <button type="button" className="remove-button" onPointerDown={() => this.deleteLink()}>
                    Remove link
                </button>
            </div>
        );

        // eslint-disable-next-line no-use-before-define
        return <ButtonDropdown view={this.view} key="link button" button={button} dropdownContent={dropdownContent} openDropdownOnButton link />;
    }

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

        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 linkAnchor1 = await Cast(linkDoc.link_anchor_1, Doc);
                            const linkAnchor2 = await Cast(linkDoc.link_anchor_2, Doc);
                            const currentDoc = DocumentView.Selected().lastElement().Document;
                            if (currentDoc && linkAnchor1 && linkAnchor2) {
                                if (Doc.AreProtosEqual(currentDoc, linkAnchor1)) {
                                    return StrCast(linkAnchor2.title);
                                }
                                if (Doc.AreProtosEqual(currentDoc, linkAnchor2)) {
                                    return StrCast(linkAnchor1.title);
                                }
                            }
                        }
                    }
                } else {
                    return href;
                }
            } else {
                return link.attrs.title;
            }
        }
        return undefined;
    }

    // TODO: should check for valid URL
    @undoBatch
    makeLinkToURL = (target: string) => {
        this.TextView?.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 as { href: string; title: string; linkId: string; targetId: string }[]).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 => aref?.href.indexOf(Doc.localServerPath()) === 0)
                    .forEach(aref => {
                        const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0];
                        anchorId && DocServer.GetRefField(anchorId).then(linkDoc => Doc.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: ButtonDropdownProps) {
        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 type="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: FormattedTextBoxProps;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
    update(view: EditorView & { TextView?: FormattedTextBox }, lastState: EditorState | undefined) {
        RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.dataDoc);
    }
    render() {
        return null;
    }
}
