import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Field, FieldType } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxBaseComponent } from '../DocComponent';
import { PinDocView, PinProps } from '../PinFuncs';
import { StyleProp } from '../StyleProp';
import { FieldView, FieldViewProps } from './FieldView';
import BigText from './LabelBigText';
import './LabelBox.scss';

@observer
export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(LabelBox, fieldKey);
    }
    public static LayoutStringWithTitle(fieldStr: string, label?: string) {
        return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; // e.g., "<ImageBox {...props} fieldKey={"data} />"
    }
    private dropDisposer?: DragManager.DragDropDisposer;
    private _timeout: any;

    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
    }

    componentDidMount() {
        this._props.setContentViewBox?.(this);
    }
    componentWillUnMount() {
        this._timeout && clearTimeout(this._timeout);
    }

    @computed get Title() {
        return Field.toString(this.dataDoc[this.fieldKey] as FieldType) || StrCast(this.Document.title);
    }

    protected createDropTarget = (ele: HTMLDivElement) => {
        this.dropDisposer?.();
        if (ele) {
            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document);
        }
    };

    get paramsDoc() {
        return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc;
    }
    specificContextMenu = (): void => {
        const funcs: ContextMenuProps[] = [];
        !Doc.noviceMode &&
            funcs.push({
                description: 'Clear Script Params',
                event: () => {
                    const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
                    params?.forEach(p => {
                        this.paramsDoc[p] = undefined;
                    });
                },
                icon: 'trash',
            });

        funcs.length && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: funcs, icon: 'mouse-pointer' });
    };

    @undoBatch
    drop = (e: Event, de: DragManager.DropEvent) => {
        const { docDragData } = de.complete;
        const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
        const missingParams = params?.filter(p => !this.paramsDoc[p]);
        if (docDragData && missingParams?.includes((e.target as any).textContent)) {
            this.paramsDoc[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) => (d.onDragStart ? docDragData.draggedDocuments[i] : d)));
            e.stopPropagation();
            return true;
        }
        return false;
    };

    @observable _mouseOver = false;
    @computed get hoverColor() {
        return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor);
    }

    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
        if (!pinProps) return this.Document;
        const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.Document.title), annotationOn: this.Document });

        if (anchor) {
            if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
            //  addAsAnnotation && this.addDocument(anchor);
            PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}) } }, this.Document);
            return anchor;
        }
        return anchor;
    };

    fitTextToBox = (
        r: any
    ):
        | NodeJS.Timeout
        | {
              rotateText: null;
              fontSizeFactor: number;
              minimumFontSize: number;
              maximumFontSize: number;
              limitingDimension: string;
              horizontalAlign: string;
              verticalAlign: string;
              textAlign: string;
              singleLine: boolean;
              whiteSpace: string;
          } => {
        const singleLine = BoolCast(this.layoutDoc._singleLine, true); // bcz: this is ugly--- should default ot false??
        const params = {
            rotateText: null,
            fontSizeFactor: 1,
            minimumFontSize: NumCast(this.layoutDoc._label_minFontSize, 8),
            maximumFontSize: NumCast(this.layoutDoc._label_maxFontSize, 1000),
            limitingDimension: 'both',
            horizontalAlign: 'center',
            verticalAlign: 'center',
            textAlign: 'center',
            singleLine,
            whiteSpace: singleLine ? 'nowrap' : 'pre-wrap',
        };
        this._timeout = undefined;
        if (!r) return params;
        if (!r.offsetHeight || !r.offsetWidth) {
            this._timeout = setTimeout(() => this.fitTextToBox(r));
            return this._timeout;
        }
        const parent = r.parentNode;
        const parentStyle = parent.style;
        parentStyle.display = '';
        parentStyle.alignItems = '';
        r.setAttribute('style', '');
        r.style.width = singleLine ? '' : '100%';

        r.style.textOverflow = 'ellipsis';
        r.style.overflow = 'hidden';
        BigText(r, params);
        return params;
    };
    _divRef: HTMLDivElement | null = null;

    // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
    render() {
        const boxParams = this.fitTextToBox(null); // this causes mobx to trigger re-render when data changes
        const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
        const missingParams = params?.filter(p => !this.paramsDoc[p]);
        params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
        const label = this.Title;
        return (
            <div
                className="labelBox-outerDiv"
                onMouseLeave={action(() => {
                    this._mouseOver = false;
                })}
                // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
                onMouseOver={action(() => {
                    this._mouseOver = true;
                })}
                ref={this.createDropTarget}
                onContextMenu={this.specificContextMenu}
                style={{ boxShadow: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow) }}>
                <div
                    className="labelBox-mainButton"
                    contentEditable
                    ref={r => (this._divRef = r)}
                    onKeyDown={e => e.stopPropagation()}
                    onKeyUp={e => {
                        e.stopPropagation();
                        e.key === 'Enter' && (this.dataDoc.text = this._divRef?.textContent ?? '');
                    }}
                    style={{
                        backgroundColor: this.hoverColor,
                        fontSize: StrCast(this.layoutDoc._text_fontSize),
                        color: StrCast(this.layoutDoc._color),
                        fontFamily: StrCast(this.layoutDoc._text_fontFamily) || 'inherit',
                        letterSpacing: StrCast(this.layoutDoc.letterSpacing),
                        textTransform: StrCast(this.layoutDoc.textTransform) as any,
                        paddingLeft: NumCast(this.layoutDoc._xPadding),
                        paddingRight: NumCast(this.layoutDoc._xPadding),
                        paddingTop: NumCast(this.layoutDoc._yPadding),
                        paddingBottom: NumCast(this.layoutDoc._yPadding),
                        width: this._props.PanelWidth(),
                        height: this._props.PanelHeight(),
                        whiteSpace: 'singleLine' in boxParams && boxParams.singleLine ? 'pre' : 'pre-wrap',
                    }}>
                    <span
                        style={{ width: 'singleLine' in boxParams ? '' : '100%' }}
                        ref={action((r: any) => {
                            this.fitTextToBox(r);
                        })}>
                        {label.startsWith('#') ? null : label.replace(/([^a-zA-Z])/g, '$1\u200b')}
                    </span>
                </div>
                <div className="labelBox-fieldKeyParams">
                    {!missingParams?.length
                        ? null
                        : missingParams.map(m => (
                              <div key={m} className="labelBox-missingParam">
                                  {m}
                              </div>
                          ))}
                </div>
            </div>
        );
    }
}

Docs.Prototypes.TemplateMap.set(DocumentType.LABEL, {
    layout: { view: LabelBox, dataField: 'title' },
    options: { acl: '', _singleLine: true, _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true },
});
Docs.Prototypes.TemplateMap.set(DocumentType.BUTTON, {
    layout: { view: LabelBox, dataField: 'title' },
    options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true },
});
