import { TextSelection } from 'prosemirror-state';
import { Attrs, Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom/client';
import * as React from 'react';
import { EditorView } from 'prosemirror-view';

// currently nothing needs to be rendered for the internal view of a summary.
export class SummaryViewInternal extends React.Component<object> {
    render() {
        return null;
    }
}

// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
// really need a react view.  However, it would be cleaner to figure out how to do this just as a react rendering
// method instead of changing prosemirror's text when the expand/elide buttons are clicked.
export class SummaryView {
    dom: HTMLSpanElement; // container for label and value
    root: ReactDOM.Root;

    constructor(node: Node, view: EditorView, getPos: () => number | undefined) {
        this.dom = document.createElement('span');
        this.dom.className = this.className(node.attrs.visibility);
        this.dom.onpointerdown = (e: PointerEvent) => {
            this.onPointerDown(e, node, view, getPos);
        };
        this.dom.onkeypress = function (e: KeyboardEvent) {
            e.stopPropagation();
        };
        this.dom.onkeydown = function (e: KeyboardEvent) {
            e.stopPropagation();
        };
        this.dom.onkeyup = function (e: KeyboardEvent) {
            e.stopPropagation();
        };
        this.dom.onmousedown = function (e: MouseEvent) {
            e.stopPropagation();
        };

        const js = node.toJSON;
        node.toJSON = function (...args: unknown[]) {
            return js.apply(this, args as []);
        };

        this.root = ReactDOM.createRoot(this.dom);
        this.root.render(<SummaryViewInternal />);
    }

    className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed');
    destroy() {
        this.root.unmount();
    }
    selectNode() {}

    updateSummarizedText(start: number, view: EditorView) {
        const mtype = view.state.schema.marks.summarize;
        const mtypeInc = view.state.schema.marks.summarizeInclusive;
        let endPos = start;

        const visited = new Set();
        for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
            let skip = false;
            // eslint-disable-next-line no-loop-func
            view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => {
                if (node.isLeaf && !visited.has(node) && !skip) {
                    if (node.marks.find(m => m.type === mtype || m.type === mtypeInc)) {
                        visited.add(node);
                        endPos = i + node.nodeSize - 1;
                    } else skip = true;
                }
            });
        }
        return TextSelection.create(view.state.doc, start, endPos);
    }

    onPointerDown = (e: PointerEvent, node: Node, view: EditorView, getPos: () => number | undefined) => {
        const visible = !node.attrs.visibility;
        const textSelection = visible //
            ? TextSelection.create(view.state.doc, (getPos() ?? 0) + 1)
            : this.updateSummarizedText((getPos() ?? 0) + 1, view); // update summarized text and save in attrs
        const text = textSelection.content();
        const attrs = { ...node.attrs, visibility: visible, ...(!visible ? { text, textslice: text.toJSON() } : {}) } as Attrs;
        view.dispatch(
            view.state.tr
                .setSelection(textSelection) // select the current summarized text (or where it will be if its collapsed)
                .replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text) // collapse/expand it
                .setNodeMarkup(getPos() ?? 0, undefined, attrs)
        ); // update the attrs
        e.preventDefault();
        e.stopPropagation();
        this.dom.className = this.className(visible);
    };
}
