import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
import { DocUtils, Docs } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import './ComparisonBox.scss';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { RichTextField } from '../../../fields/RichTextField';
import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
import { DocData } from '../../../fields/DocSymbols';

@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(ComparisonBox, fieldKey);
    }
    private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
    }

    @observable _animating = '';

    @computed get clipWidth() {
        return NumCast(this.layoutDoc[this.clipWidthKey], 50);
    }
    get clipWidthKey() {
        return '_' + this._props.fieldKey + '_clipWidth';
    }
    componentDidMount() {
        this._props.setContentViewBox?.(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 = (e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
        if (dropEvent.complete.docDragData) {
            const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
            const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.Document, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
            Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc);
            !added && e.preventDefault();
            e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
            return added;
        }
    };

    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.ScreenToLocalBoxXf().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.Document.title,
            // set presentation timing properties for restoring view
            presentation_transition: 1000,
            annotationOn: this.Document,
        });
        if (anchor) {
            if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
            /* addAsAnnotation &&*/ this.addDocument(anchor);
            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document);
            return anchor;
        }
        return this.Document;
    };

    @undoBatch
    clearDoc = (fieldKey: string) => 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) => {
        if (this.dataDoc[which]) return false;
        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));

    closeDown = (e: React.PointerEvent, which: string) => {
        setupMoveUpEvents(
            this,
            e,
            e => {
                const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move);
                de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
                    this.clearDoc(which);
                    return addDocument(doc);
                };
                de.canEmbed = true;
                DragManager.StartDocumentDrag([this._closeRef.current!], de, e.clientX, e.clientY);
                return true;
            },
            emptyFunction,
            e => this.clearDoc(which)
        );
    };
    docStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, 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);
    _closeRef = React.createRef<HTMLDivElement>();
    render() {
        const clearButton = (which: string) => {
            return (
                <div
                    ref={this._closeRef}
                    className={`clear-button ${which}`}
                    onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
                >
                    <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);
            const subjectText = RTFCast(this.Document[this.fieldKey])?.Text;
            // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot
            // of if there is no Doc in the second comparison slot, but the second slot has a RichTextField, then render a text box to show the contents of the document's field key slot
            const layoutTemplateString = !targetDoc
                ? which.endsWith('1') && subjectText !== undefined
                    ? FormattedTextBox.LayoutString(this.fieldKey)
                    : which.endsWith('2') && (this.Document[which] instanceof RichTextField || typeof this.Document[which] === 'string')
                      ? FormattedTextBox.LayoutString(which)
                      : undefined
                : undefined;

            // A bit hacky to try out the concept of using GPT to fill in flashcards -- this whole process should probably be packaged into a script to be more generic.
            //   If the second slot doesn't have anything in it, but the fieldKey slot has text
            // and the fieldKey + "_alternate" has a text that incldues "--TEXT--", then
            // treat the fieldKey + "_altenrate" text as a GPT query parameterized by the fieldKey text
            // Call GPT to fill in an "answer" value in the second slot.
            if (which.endsWith('2') && !layoutTemplateString && !targetDoc) {
                const queryText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text;
                if (queryText?.includes('--TEXT--') && subjectText) {
                    this.Document[DocData][this.fieldKey + '_2'] = '';
                    gptAPICall(queryText?.replace('--TEXT--', subjectText), GPTCallType.COMPLETION).then(value => (this.Document[DocData][this.fieldKey + '_2'] = value.trim()));
                }
            }
            return targetDoc || layoutTemplateString ? (
                <>
                    <DocumentView
                        {...this._props}
                        renderDepth={this.props.renderDepth + 1}
                        LayoutTemplateString={layoutTemplateString}
                        Document={layoutTemplateString ? this.Document : targetDoc}
                        containerViewPath={this.DocumentView?.().docViewPath}
                        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}
                    />
                    {layoutTemplateString ? null : 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>
        );
    }
}
