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

@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 { droppedDocuments } = dropEvent.complete.docDragData;
            const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(toList(doc).lastElement(), fieldKey));
            Doc.SetContainer(droppedDocuments.lastElement(), this.dataDoc);
            !added && e.preventDefault();
            e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
            return added;
        }
        return undefined;
    };

    private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
        if (e.button !== 2) {
            setupMoveUpEvents(
                this,
                e,
                this.onPointerMove,
                emptyFunction,
                action((moveEv, doubleTap) => {
                    if (doubleTap) {
                        this._isAnyChildContentActive = true;
                        if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
                        if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) 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;
    };

    closeDown = (e: React.PointerEvent, which: string) => {
        setupMoveUpEvents(
            this,
            e,
            moveEv => {
                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, moveEv.clientX, moveEv.clientY);
                return true;
            },
            emptyFunction,
            () => 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 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
    moveDoc2 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
    remDoc1 = (docs: Doc | Doc[]) => toList(docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
    remDoc2 = (docs: Doc | Doc[]) => toList(docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);

    /**
     * Tests for whether a comparison box slot (ie, before or after) has renderable text content
     * @param whichSlot field key for start or end slot
     * @returns a JSX layout string if a text field is found, othwerise undefined
     */
    testForTextFields = (whichSlot: string) => {
        const slotData = Doc.Get(this.dataDoc, whichSlot, true);
        const slotHasText = slotData instanceof RichTextField || typeof slotData === 'string';
        const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
        const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim();
        const layoutTemplateString =
            slotHasText ? FormattedTextBox.LayoutString(whichSlot):
            whichSlot.endsWith('1') ? (subjectText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey) : undefined) :
                                       altText !== undefined     ? FormattedTextBox.LayoutString(this.fieldKey + '_alternate'): undefined; // prettier-ignore

        // A bit hacky to try out the concept of using GPT to fill in flashcards
        //   If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string)
        // and the fieldKey + "_alternate" has text that includes a GPT query (indicated by (( && )) ) that is parameterized (optionally) by the fieldKey text (this) or other metadata (this.<field>).
        // eg., this.text_alternate is
        //     "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))"
        //     where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
        // The GPT call will put the "answer"  in the second slot of the comparison (eg., text_2)
        if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) {
            const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
            if (queryText?.match(/\(\(.*\)\)/)) {
                KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
            }
        }
        return layoutTemplateString;
    };

    _closeRef = React.createRef<HTMLDivElement>();
    render() {
        const clearButton = (which: string) => (
            <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>
        );

        /**
         * Display the Docs in the before/after fields of the comparison.  This also supports a GPT flash card use case
         * where if there are no Docs in the slots, but the main fieldKey contains text, then
         * @param whichSlot
         * @returns
         */
        const displayDoc = (whichSlot: string) => {
            const whichDoc = DocCast(this.dataDoc[whichSlot]);
            const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
            const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot);
            return targetDoc || layoutString ? (
                <>
                    <DocumentView
                        // eslint-disable-next-line react/jsx-props-no-spreading
                        {...this._props}
                        ignoreUsePath={layoutString ? true : undefined}
                        renderDepth={this.props.renderDepth + 1}
                        LayoutTemplateString={layoutString}
                        Document={layoutString ? this.Document : targetDoc}
                        containerViewPath={this.DocumentView?.().docViewPath}
                        moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                        removeDocument={whichSlot.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
                        pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
                    />
                    {layoutString ? null : clearButton(whichSlot)}
                </> // 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) => (
            <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>
        );
    }
}
