import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt } from '../../../fields/Doc';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import './ComparisonBox.scss';
import { DocumentView, DocumentViewProps } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
import React = require('react');

@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(ComparisonBox, fieldKey);
    }
    protected _multiTouchDisposer?: import('../../util/InteractionUtils').InteractionUtils.MultiTouchEventDisposer | undefined;
    private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];

    @observable _animating = '';

    @computed get clipWidth() {
        return NumCast(this.layoutDoc[this.clipWidthKey], 50);
    }
    get clipWidthKey() {
        return '_' + this.props.fieldKey + '_clipWidth';
    }
    componentDidMount() {
        this.props.setContentView?.(this);
    }
    protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
        this._disposers[disposerId]?.();
        if (ele) {
            this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.internalDrop(e, dropEvent, fieldKey), this.layoutDoc);
        }
    };

    @undoBatch
    private internalDrop = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
        if (dropEvent.complete.docDragData) {
            event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
            const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
            const res = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.rootDoc, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
            res && (droppedDocs.lastElement().embedContainer = this.dataDoc);
            return res;
        }
    };

    private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
        if (e.button !== 2) {
            setupMoveUpEvents(
                this,
                e,
                this.onPointerMove,
                emptyFunction,
                action((e, doubleTap) => {
                    if (doubleTap) {
                        this._isAnyChildContentActive = true;
                        if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
                        if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
                    }
                }),
                false,
                undefined,
                action(() => {
                    if (this._isAnyChildContentActive) return;
                    this._animating = 'all 200ms';
                    // on click, animate slider movement to the targetWidth
                    this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this.props.PanelWidth();
                    setTimeout(
                        action(() => (this._animating = '')),
                        200
                    );
                })
            );
        }
    };

    @action
    private onPointerMove = ({ movementX }: PointerEvent) => {
        const width = movementX * this.props.ScreenToLocalTransform().Scale + (this.clipWidth / 100) * this.props.PanelWidth();
        if (width && width > 5 && width < this.props.PanelWidth()) {
            this.layoutDoc[this.clipWidthKey] = (width * 100) / this.props.PanelWidth();
        }
        return false;
    };

    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
        const anchor = Docs.Create.ConfigDocument({
            title: 'CompareAnchor:' + this.rootDoc.title,
            // set presentation timing properties for restoring view
            presTransition: 1000,
            annotationOn: this.rootDoc,
        });
        if (anchor) {
            if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
            /* addAsAnnotation &&*/ this.addDocument(anchor);
            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.rootDoc);
            return anchor;
        }
        return this.rootDoc;
    };

    @undoBatch
    clearDoc = (e: React.MouseEvent, fieldKey: string) => {
        e.stopPropagation; // prevent click event action (slider movement) in registerSliding
        delete this.dataDoc[fieldKey];
    };
    moveDoc = (doc: Doc, addDocument: (document: Doc | Doc[]) => boolean, which: string) => this.remDoc(doc, which) && addDocument(doc);
    addDoc = (doc: Doc, which: string) => {
        this.dataDoc[which] = doc;
        return true;
    };
    remDoc = (doc: Doc, which: string) => {
        if (this.dataDoc[which] === doc) {
            this.dataDoc[which] = undefined;
            return true;
        }
        return false;
    };

    whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive));

    docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
        if (property === StyleProp.PointerEvents) return 'none';
        return this.props.styleProvider?.(doc, props, property);
    };
    moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
    moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
    remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
    remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
    render() {
        const clearButton = (which: string) => {
            return (
                <div
                    className={`clear-button ${which}`}
                    onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
                    onClick={e => this.clearDoc(e, which)}>
                    <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
                </div>
            );
        };
        const displayDoc = (which: string) => {
            const whichDoc = DocCast(this.dataDoc[which]);
            const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
            return targetDoc ? (
                <>
                    <DocumentView
                        {...this.props}
                        Document={targetDoc}
                        DataDoc={undefined}
                        moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                        removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                        NativeWidth={returnZero}
                        NativeHeight={returnZero}
                        isContentActive={emptyFunction}
                        isDocumentActive={returnFalse}
                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                        styleProvider={this._isAnyChildContentActive ? this.props.styleProvider : this.docStyleProvider}
                        hideLinkButton={true}
                        pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
                    />
                    {clearButton(which)}
                </> // placeholder image if doc is missing
            ) : (
                <div className="placeholder">
                    <FontAwesomeIcon className="upload-icon" icon="cloud-upload-alt" size="lg" />
                </div>
            );
        };
        const displayBox = (which: string, index: number, cover: number) => {
            return (
                <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
                    {displayDoc(which)}
                </div>
            );
        };

        return (
            <div className={`comparisonBox${this.props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
                {displayBox(`${this.fieldKey}_2`, 1, this.props.PanelWidth() - 3)}
                <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
                    {displayBox(`${this.fieldKey}_1`, 0, 0)}
                </div>

                <div
                    className="slide-bar"
                    style={{
                        left: `calc(${this.clipWidth + '%'} - 0.5px)`,
                        cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? 'w-resize' : undefined,
                    }}
                    onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
                >
                    <div className="slide-handle" />
                </div>
            </div>
        );
    }
}
