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 { Node } from 'prosemirror-model';
import { NodeSelection } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
import { Cast, DocCast } from '../../../../fields/Types';
import { emptyFunction } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { DocumentOptions, FInfo } from '../../../documents/Documents';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { Transform } from '../../../util/Transform';
import { undoable, undoBatch } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
import { FilterPanel } from '../../FilterPanel';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { OpenWhere } from '../OpenWhere';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';

@observer
export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
    // eslint-disable-next-line no-use-before-define
    static Instance: DashFieldViewMenu;
    static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
    static toggleFieldHide: () => void = emptyFunction;
    static toggleValueHide: () => void = emptyFunction;
    constructor(props: AntimodeMenuProps) {
        super(props);
        DashFieldViewMenu.Instance = this;
    }

    showFields = (e: React.MouseEvent) => {
        DashFieldViewMenu.createFieldView(e);
        DashFieldViewMenu.Instance.fadeOut(true);
    };
    toggleFieldHide = () => {
        DashFieldViewMenu.toggleFieldHide();
        DashFieldViewMenu.Instance.fadeOut(true);
    };
    toggleValueHide = () => {
        DashFieldViewMenu.toggleValueHide();
        DashFieldViewMenu.Instance.fadeOut(true);
    };

    @observable _fieldKey = '';

    @action
    public show = (x: number, y: number, fieldKey: string) => {
        this._fieldKey = fieldKey;
        this.jumpTo(x, y, true);
        const hideMenu = () => {
            this.fadeOut(true);
            document.removeEventListener('pointerdown', hideMenu, true);
        };
        document.addEventListener('pointerdown', hideMenu, true);
    };
    render() {
        return this.getElement(
            <>
                <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
                    <button type="button" className="antimodeMenu-button" onPointerDown={this.showFields}>
                        <FontAwesomeIcon icon="eye" size="sm" />
                    </button>
                </Tooltip>
                {this._fieldKey.startsWith('#') ? null : (
                    <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
                        <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
                            <FontAwesomeIcon icon="bullseye" size="sm" />
                        </button>
                    </Tooltip>
                )}
                {this._fieldKey.startsWith('#') ? null : (
                    <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
                        <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
                            <FontAwesomeIcon icon="hashtag" size="sm" />
                        </button>
                    </Tooltip>
                )}
            </>
        );
    }
}
interface IDashFieldViewInternal {
    fieldKey: string;
    docId: string;
    hideKey: boolean;
    hideValue: boolean;
    tbox: FormattedTextBox;
    width: number;
    height: number;
    editable: boolean;
    node: Node;
    getPos: () => number;
    unclickable: () => boolean;
}

@observer
export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldViewInternal> {
    _reactionDisposer: IReactionDisposer | undefined;
    _textBoxDoc: Doc;
    _fieldKey: string;
    _fieldRef = React.createRef<HTMLDivElement>();
    @observable _dashDoc: Doc | undefined = undefined;
    @observable _expanded = false;

    constructor(props: IDashFieldViewInternal) {
        super(props);
        makeObservable(this);
        this._fieldKey = this._props.fieldKey;
        this._textBoxDoc = this._props.tbox.Document;
        const setDoc = action((doc: Doc) => {
            this._dashDoc = doc;
        });

        if (this._props.docId) {
            DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
        } else {
            setDoc(this._props.tbox.Document);
        }
    }

    componentDidMount() {
        this._reactionDisposer = reaction(
            () => (this._dashDoc ? Field.toKeyValueString(this._dashDoc, this._props.fieldKey) : undefined),
            keyvalue => keyvalue && this._props.tbox.tryUpdateDoc(true)
        );
    }

    componentWillUnmount() {
        this._reactionDisposer?.();
    }
    isRowActive = () => this._props.tbox._props.isContentActive() && this._props.editable;
    finishEdit = action(() => {
        if (this._expanded) {
            this._expanded = false;
            // if the edit finishes, then we want to lose focus on the textBox unless something else in the textBox got focus
            // the timeout allows switching focus from one dashFieldView to another in the same text box
            setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.());
        }
    });
    selectedCells = () => (this._dashDoc ? [this._dashDoc] : undefined);
    columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey

    finfo = (fieldKey: string) => (new DocumentOptions() as Record<string, FInfo>)[fieldKey];

    // set the display of the field's value (checkbox for booleans, span of text for strings)
    @computed get fieldValueContent() {
        return !this._dashDoc ? null : (
            <div
                className="dashFieldView-fieldSpan"
                onPointerDown={action(() => {
                    this._expanded = !this._props.editable ? false : !this._expanded;
                })}
                ref={this._valueRef}
                style={{ width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
                <SchemaTableCell
                    Document={this._dashDoc}
                    col={0}
                    deselectCell={emptyFunction}
                    selectCell={emptyFunction}
                    maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth}
                    columnWidth={returnZero}
                    selectedCells={this.selectedCells}
                    selectedCol={returnZero}
                    fieldKey={this._fieldKey}
                    highlightCells={emptyFunction} // fix
                    refSelectModeInfo={{ enabled: false, currEditing: undefined }} // fix
                    selectReference={emptyFunction} //
                    eqHighlightFunc={() => []} // fix
                    isolatedSelection={() => [true, true]} // fix
                    rowSelected={returnTrue} //fix
                    rowHeight={returnZero}
                    isRowActive={this.isRowActive}
                    padding={0}
                    getFinfo={this.finfo}
                    setColumnValues={returnFalse}
                    allowCRs
                    oneLine={!this._expanded}
                    finishEdit={this.finishEdit}
                    transform={Transform.Identity}
                    menuTarget={null}
                    autoFocus
                    rootSelected={this._props.tbox._props.rootSelected}
                />
            </div>
        );
    }

    createPivotForField = () => {
        const container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
        if (container) {
            const embedding = Doc.MakeEmbedding(container.Document);
            embedding._type_collection = CollectionViewType.Pivot;
            const colHdrKey = '_' + container.LayoutFieldKey + '_columnHeaders';
            let list = Cast(embedding[colHdrKey], listSpec(SchemaHeaderField));
            if (!list) {
                embedding[colHdrKey] = list = new List<SchemaHeaderField>();
            }
            list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
            list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
            embedding._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey;
            this._props.tbox._props.addDocTab(embedding, OpenWhere.addRight);
        }
    };

    toggleFieldHide = undoable(
        action(() => {
            const editor = this._props.tbox.EditorView!;
            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey }));
        }),
        'hideKey'
    );

    toggleValueHide = undoable(
        action(() => {
            const editor = this._props.tbox.EditorView!;
            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue }));
        }),
        'hideValue'
    );

    @computed get _hideKey() {
        return this._props.hideKey && !this._expanded;
    }

    @computed get _hideValue() {
        return this._props.hideValue;
    }

    // clicking on the label creates a pivot view collection of all documents
    // in the same collection.  The pivot field is the fieldKey of this label
    onPointerDownLabelSpan = (e: React.PointerEvent) => {
        setupMoveUpEvents(this, e, returnFalse, returnFalse, moveEv => {
            DashFieldViewMenu.createFieldView = this.createPivotForField;
            DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
            DashFieldViewMenu.toggleValueHide = this.toggleValueHide;
            DashFieldViewMenu.Instance.show(moveEv.clientX, moveEv.clientY + 16, this._fieldKey);
            const editor = this._props.tbox.EditorView!;
            setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100);
        });
    };

    @undoBatch
    selectVal = (event: React.ChangeEvent<HTMLSelectElement> | undefined) => {
        event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value === '-unset-' ? undefined : event.target.value);
    };

    @computed get values() {
        const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []);

        return vals.strings.map(facet => ({ value: facet, label: facet }));
    }

    _valueRef = React.createRef<HTMLDivElement>();

    render() {
        return (
            <div
                className={`dashFieldView${this.isRowActive() ? '-active' : ''}`}
                ref={this._fieldRef}
                style={{
                    // width: this._props.width,
                    height: this._props.height,
                    pointerEvents: this._props.tbox._props.rootSelected?.() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
                }}>
                {this._hideKey ? null : (
                    <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
                        {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : (this._dashDoc?.title ?? '') + ':') + this._fieldKey}
                    </span>
                )}
                {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
                {!this.values.length ? null : (
                    <select className="dashFieldView-select" tabIndex={-1} defaultValue={this._dashDoc && Field.toKeyValueString(this._dashDoc, this._fieldKey)} onChange={this.selectVal}>
                        <option value="-unset-">-unset-</option>
                        {this.values.map(val => (
                            <option key={val.value} value={val.value}>
                                {val.label}
                            </option>
                        ))}
                    </select>
                )}
            </div>
        );
    }
}
export class DashFieldView {
    dom: HTMLDivElement; // container for label and value
    root: ReactDOM.Root;
    node: Node;
    tbox: FormattedTextBox;
    getpos: () => number | undefined;

    unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some(m => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
    constructor(node: Node, view: EditorView, getPos: () => number | undefined, tbox: FormattedTextBox) {
        makeObservable(this);
        const getPosition = () => getPos() ?? 0;
        this.node = node;
        this.tbox = tbox;
        this.getpos = getPos;
        this.dom = document.createElement('div');
        this.dom.style.width = node.attrs.width;
        this.dom.style.height = node.attrs.height;
        this.dom.style.position = 'relative';
        this.dom.style.display = 'inline-flex';
        this.dom.onkeypress = function (e: KeyboardEvent) {
            e.stopPropagation();
        };
        this.dom.onkeydown = action((e: KeyboardEvent) => {
            e.stopPropagation();
        });
        this.dom.onkeyup = function (e: KeyboardEvent) {
            e.stopPropagation();
        };
        this.dom.onmousedown = function (e: MouseEvent) {
            e.stopPropagation();
        };

        this.root = ReactDOM.createRoot(this.dom);
        this.root.render(
            <DashFieldViewInternal
                node={node}
                unclickable={this.unclickable}
                getPos={getPosition}
                fieldKey={node.attrs.fieldKey}
                docId={node.attrs.docId}
                width={node.attrs.width}
                height={node.attrs.height}
                hideKey={node.attrs.hideKey}
                hideValue={node.attrs.hideValue}
                editable={node.attrs.editable}
                tbox={tbox}
            />
        );
    }
    destroy() {
        setTimeout(() => {
            try {
                this.root.unmount();
            } catch {
                /* empty */
            }
        });
    }
    deselectNode() {}
    selectNode() {}
}
