import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { action, computed, IReactionDisposer, observable } from 'mobx';
import { observer } from 'mobx-react';
import { NodeSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom/client';
import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
import { ComputedField } from '../../../../fields/ScriptField';
import { Cast, StrCast } from '../../../../fields/Types';
import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
import React = require('react');

export class DashFieldView {
    dom: HTMLDivElement; // container for label and value
    root: any;
    node: any;
    tbox: FormattedTextBox;

    unclickable = () => !this.tbox.props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
        this.node = node;
        this.tbox = tbox;
        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-block';
        this.dom.onkeypress = function (e: any) {
            e.stopPropagation();
        };
        this.dom.onkeydown = function (e: any) {
            e.stopPropagation();
        };
        this.dom.onkeyup = function (e: any) {
            e.stopPropagation();
        };
        this.dom.onmousedown = function (e: any) {
            e.stopPropagation();
        };

        this.root = ReactDOM.createRoot(this.dom);
        this.root.render(
            <DashFieldViewInternal
                node={node}
                unclickable={this.unclickable}
                getPos={getPos}
                fieldKey={node.attrs.fieldKey}
                docId={node.attrs.docId}
                width={node.attrs.width}
                height={node.attrs.height}
                hideKey={node.attrs.hideKey}
                editable={node.attrs.editable}
                tbox={tbox}
            />
        );
    }
    destroy() {
        setTimeout(() => {
            try {
                this.root.unmount();
            } catch {}
        });
    }
    deselectNode() {
        this.dom.classList.remove('ProseMirror-selectednode');
    }
    selectNode() {
        this.dom.classList.add('ProseMirror-selectednode');
    }
}

interface IDashFieldViewInternal {
    fieldKey: string;
    docId: string;
    hideKey: boolean;
    tbox: FormattedTextBox;
    width: number;
    height: number;
    editable: boolean;
    node: any;
    getPos: any;
    unclickable: () => boolean;
}

@observer
export class DashFieldViewInternal extends React.Component<IDashFieldViewInternal> {
    _reactionDisposer: IReactionDisposer | undefined;
    _textBoxDoc: Doc;
    _fieldKey: string;
    _fieldStringRef = React.createRef<HTMLSpanElement>();
    @observable _dashDoc: Doc | undefined;

    constructor(props: IDashFieldViewInternal) {
        super(props);
        this._fieldKey = this.props.fieldKey;
        this._textBoxDoc = this.props.tbox.props.Document;

        if (this.props.docId) {
            DocServer.GetRefField(this.props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
        } else {
            this._dashDoc = this.props.tbox.rootDoc;
        }
    }
    componentWillUnmount() {
        this._reactionDisposer?.();
    }

    public static multiValueDelimeter = ';';
    public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) {
        const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? '';
        const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(':=') || dashVal === '' ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
        return { boolVal: Cast(fval, 'boolean', null), strVal: Field.toString(fval as Field) || '' };
    }

    // set the display of the field's value (checkbox for booleans, span of text for strings)
    @computed get fieldValueContent() {
        if (this._dashDoc) {
            const { boolVal, strVal } = DashFieldViewInternal.fieldContent(this._textBoxDoc, this._dashDoc, this._fieldKey);
            // field value is a boolean, so use a checkbox or similar widget to display it
            if (boolVal === true || boolVal === false) {
                return (
                    <input
                        className="dashFieldView-fieldCheck"
                        type="checkbox"
                        checked={boolVal}
                        onChange={e => {
                            if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked;
                            Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true);
                        }}
                    />
                );
            } // field value is a string, so display it as an editable span
            else {
                // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
                // use React events.  Essentially, React events occur after native events have been processed, so corresponding React events
                // will never fire because Prosemirror has handled the native events.  So we add listeners for native events here.
                return (
                    <span
                        className="dashFieldView-fieldSpan"
                        contentEditable={!this.props.unclickable()}
                        style={{ display: strVal.length < 2 ? 'inline-block' : undefined }}
                        suppressContentEditableWarning={true}
                        defaultValue={strVal}
                        ref={r => {
                            r?.addEventListener('keydown', e => this.fieldSpanKeyDown(e, r));
                            r?.addEventListener('blur', e => r && this.updateText(r.textContent!, false));
                            r?.addEventListener(
                                'pointerdown',
                                action(e => {
                                    // let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
                                    // while (target && !target.dataset?.targethrefs) target = target.parentElement;
                                    this.props.tbox.EditorView!.dispatch(this.props.tbox.EditorView!.state.tr.setSelection(new NodeSelection(this.props.tbox.EditorView!.state.doc.resolve(this.props.getPos()))));
                                    // FormattedTextBoxComment.update(this.props.tbox, this.props.tbox.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc);
                                    // e.stopPropagation();
                                })
                            );
                        }}>
                        {strVal}
                    </span>
                );
            }
        }
    }

    // we need to handle all key events on the input span or else they will propagate to prosemirror.
    @action
    fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
        if (e.key === 'c' && (e.ctrlKey || e.metaKey)) {
            navigator.clipboard.writeText(window.getSelection()?.toString() || '');
            return;
        }
        if (e.key === 'Enter') {
            // handle the enter key by "submitting" the current text to Dash's database.
            this.updateText(span.textContent!, true);
            e.preventDefault(); // prevent default to avoid a newline from being generated and wiping out this field view
        }
        if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
            // handle ctrl-A to select all the text within the span
            if (window.getSelection) {
                const range = document.createRange();
                range.selectNodeContents(span);
                window.getSelection()!.removeAllRanges();
                window.getSelection()!.addRange(range);
            }
            e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected
        }
        if (!this.props.editable) {
            e.preventDefault();
        }
        e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror.
    };

    @action
    updateText = (nodeText: string, forceMatch: boolean) => {
        if (nodeText) {
            const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText;
            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
            // holds the different enumerated values for the field in the titles of its collected documents.
            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
            DocServer.GetRefField(this._fieldKey).then(options => {
                let modText = '';
                options instanceof Doc && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
                if (modText) {
                    //  elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
                    Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true);
                } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
                else if (nodeText.startsWith(':=')) {
                    this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
                } else if (nodeText.startsWith('=:=')) {
                    Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
                } else {
                    if (Number(newText).toString() === newText) {
                        if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
                        Doc.SetInPlace(this._dashDoc!, this._fieldKey, Number(newText), true);
                    } else {
                        const splits = newText.split(DashFieldViewInternal.multiValueDelimeter);
                        if (!this._textBoxDoc[this._fieldKey]) {
                            const strVal = splits.length > 1 ? new List<string>(splits) : newText;
                            if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
                            Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true);
                        }
                    }
                }
            });
        }
    };

    createPivotForField = (e: React.MouseEvent) => {
        let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement();
        if (container) {
            const alias = Doc.MakeAlias(container.props.Document);
            alias._viewType = CollectionViewType.Time;
            let list = Cast(alias._columnHeaders, listSpec(SchemaHeaderField));
            if (!list) {
                alias._columnHeaders = 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'));
            alias._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey;
            this.props.tbox.props.addDocTab(alias, OpenWhere.addRight);
        }
    };

    // 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: any) => {
        setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
            DashFieldViewMenu.createFieldView = this.createPivotForField;
            DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
        });
    };

    render() {
        return (
            <div
                className="dashFieldView"
                style={{
                    width: this.props.width,
                    height: this.props.height,
                }}>
                {this.props.hideKey ? null : (
                    <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
                        {this._fieldKey}
                    </span>
                )}

                {this.props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
            </div>
        );
    }
}
@observer
export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
    static Instance: DashFieldViewMenu;
    static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
    constructor(props: any) {
        super(props);
        DashFieldViewMenu.Instance = this;
    }
    @action
    showFields = (e: React.MouseEvent) => {
        DashFieldViewMenu.createFieldView(e);
        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 className="antimodeMenu-button" onPointerDown={this.showFields}>
                    <FontAwesomeIcon icon="eye" size="lg" />
                </button>
            </Tooltip>
        );
    }
}
