import { TextSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom/client';
import * as React from 'react';
import { IReactionDisposer, computed, reaction } from 'mobx';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
import { NumCast } from '../../../../fields/Types';
import { Node } from 'prosemirror-model';
import { EditorView } from 'prosemirror-view';

interface IDashDocCommentViewInternal {
    docId: string;
    view: EditorView;
    getPos: () => number;
    setHeight: (height: number) => void;
}

export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
    _reactionDisposer: IReactionDisposer | undefined;

    constructor(props: IDashDocCommentViewInternal) {
        super(props);
        this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
        this.onPointerEnterCollapsed = this.onPointerEnterCollapsed.bind(this);
        this.onPointerUpCollapsed = this.onPointerUpCollapsed.bind(this);
        this.onPointerDownCollapsed = this.onPointerDownCollapsed.bind(this);
    }
    componentDidMount(): void {
        this._reactionDisposer?.();
        this._dashDoc.then(doc => {
            if (doc instanceof Doc) {
                this._reactionDisposer = reaction(
                    () => NumCast((doc as Doc)._height),
                    hgt => this.props.setHeight(hgt),
                    { fireImmediately: true }
                );
            }
        });
    }
    componentWillUnmount(): void {
        this._reactionDisposer?.();
    }

    @computed get _dashDoc() {
        return DocServer.GetRefField(this.props.docId);
    }

    onPointerLeaveCollapsed = (e: React.PointerEvent) => {
        this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
        e.preventDefault();
        e.stopPropagation();
    };

    onPointerEnterCollapsed = (e: React.PointerEvent) => {
        this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
        e.preventDefault();
        e.stopPropagation();
    };

    onPointerUpCollapsed = (e: React.PointerEvent) => {
        const target = this.targetNode();

        if (target) {
            const expand = target.hidden;
            const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: !target.node.attrs.hidden });
            this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
            setTimeout(() => {
                expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
                try {
                    this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, (this.props.getPos() ?? 0) + (expand ? 2 : 1))));
                } catch {
                    /* empty */
                }
            }, 0);
        }
        e.stopPropagation();
    };

    onPointerDownCollapsed = (e: React.PointerEvent) => {
        e.stopPropagation();
    };

    targetNode = () => {
        // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
        const { state } = this.props.view;
        for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
            const m = state.doc.nodeAt(i);
            if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docId === this.props.docId) {
                return { node: m, pos: i, hidden: m.attrs.hidden } as { node: Node; pos: number; hidden: boolean };
            }
        }

        const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: this.props.docId, float: 'right' });
        this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc));
        setTimeout(() => {
            try {
                this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
            } catch {
                /* empty */
            }
        }, 0);
        return undefined;
    };

    render() {
        return (
            <span
                className="formattedTextBox-inlineComment"
                id={'DashDocCommentView-' + this.props.docId}
                onPointerLeave={this.onPointerLeaveCollapsed}
                onPointerEnter={this.onPointerEnterCollapsed}
                onPointerUp={this.onPointerUpCollapsed}
                onPointerDown={this.onPointerDownCollapsed}
            />
        );
    }
}

// creates an inline comment in a note when '>>' is typed.
// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
// the comment can be toggled on/off with the '<-' text anchor.
export class DashDocCommentView {
    dom: HTMLDivElement; // container for label and value
    root: ReactDOM.Root;
    node: Node;

    constructor(node: Node, view: EditorView, getPos: () => number | undefined) {
        this.node = node;
        this.dom = document.createElement('div');
        this.dom.style.width = node.attrs.width;
        this.dom.style.height = node.attrs.height;
        this.dom.style.fontWeight = 'bold';
        this.dom.style.position = 'relative';
        this.dom.style.display = 'inline-block';
        this.dom.onkeypress = function (e) {
            e.stopPropagation();
        };
        this.dom.onkeydown = function (e) {
            e.stopPropagation();
        };
        this.dom.onkeyup = function (e) {
            e.stopPropagation();
        };
        this.dom.onmousedown = function (e) {
            e.stopPropagation();
        };

        const getPosition = () => getPos() ?? 0;
        this.root = ReactDOM.createRoot(this.dom);
        this.root.render(<DashDocCommentViewInternal view={view} getPos={getPosition} setHeight={this.setHeight} docId={node.attrs.docId} />);
    }

    setHeight = (hgt: number) => {
        !this.node.attrs.reflow &&
            DocServer.GetRefField(this.node.attrs.docId).then(doc => {
                doc instanceof Doc && (this.dom.style.height = hgt + '');
            });
    };

    destroy() {
        this.root.unmount();
    }
    deselectNode() {
        this.dom.classList.remove('ProseMirror-selectednode');
    }
    selectNode() {
        this.dom.classList.add('ProseMirror-selectednode');
    }
}
