import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { StopEvent, returnOne, returnZero } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { PinDocView, PinProps } from '../PinFuncs';
import { StyleProp } from '../StyleProp';
import { DocumentView } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { FocusViewOptions } from '../nodes/FocusViewOptions';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import './CollectionCarouselView.scss';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
import { DocData } from '../../../fields/DocSymbols';

@observer
export class CollectionCarouselView extends CollectionSubView() {
    private _dropDisposer?: DragManager.DragDropDisposer;

    _oldWheel: HTMLElement | null = null;
    _fadeTimer: NodeJS.Timeout | undefined;
    @observable _last_index = this.carouselIndex;
    @observable _last_opacity = 1;

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

    componentDidMount(): void {
        this._props.setContentViewBox?.(this);
    }
    componentWillUnmount() {
        this._dropDisposer?.();
    }

    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
        this._dropDisposer?.();
        if (ele) {
            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
        }
        this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
        this._oldWheel = ele;
        // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
        ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
    };

    @computed get captionMarginX(){ return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore
    @computed get carouselIndex() { return NumCast(this.layoutDoc._carousel_index) % this.carouselItems.length; } // prettier-ignore
    @computed get carouselItems() { return this.childLayoutPairs.filter(pair => !pair.layout.layout_isSvg); } // prettier-ignore

    /**
     * Move forward or backward the specified number of Docs
     * @param dir signed number indicating Docs to move forward or backward
     */
    move = action((dir: number) => {
        this._last_index = this.carouselIndex;
        this.layoutDoc._carousel_index = this.carouselItems.length ? (this.carouselIndex + dir + this.carouselItems.length) % this.carouselItems.length : 0;
    });

    /**
     * Goes to the next Doc in the stack subject to the currently selected filter option.
     */
    advance = () => this.move(1);

    /**
     * Goes to the previous Doc in the stack subject to the currently selected filter option.
     */
    goback = () => this.move(-1);

    curDoc = () => this.carouselItems[this.carouselIndex]?.layout;

    focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => {
        const docs = DocListCast(this.Document[this.fieldKey]);
        if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) {
            const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor));
            options.didMove = newIndex !== this.layoutDoc._carousel_index;
            options.didMove && (this.layoutDoc._carousel_index = newIndex);
        }
        return undefined;
    };

    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
        const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.carouselIndex });
        PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { collectionType: true, filters: true } }, this.Document);
        addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
        return anchor;
    };
    addDocTab = this.addLinkedDocTab;

    captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string) => {
        // first look for properties on the document in the carousel, then fallback to properties on the container
        const childValue = doc?.['caption_' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
        return childValue ?? this._props.styleProvider?.(this.layoutDoc, captionProps, property);
    };
    contentPanelWidth = () => (this._props.PanelWidth() - 2 * NumCast(this.layoutDoc.xMargin)) / this.nativeScaling();
    contentPanelHeight = () => (this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0) - 2 * NumCast(this.layoutDoc.yMargin)) / this.nativeScaling();
    onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
    onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
    captionWidth = () => this._props.PanelWidth() - 2 * this.captionMarginX;
    contentScreenToLocalXf = () =>
        this._props
            .ScreenToLocalTransform() //
            .translate(-NumCast(this.layoutDoc.xMargin) / this.nativeScaling(), -NumCast(this.layoutDoc.yMargin) / this.nativeScaling());
    isChildContentActive = () =>
        this._props.isContentActive?.() === false
            ? false
            : this._props.isContentActive()
              ? true
              : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
                ? false
                : undefined; // prettier-ignore
    onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
    renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => {
        return (
            <DocumentView
                {...this._props}
                ref={overlayFunc}
                Document={doc}
                NativeWidth={returnZero}
                NativeHeight={returnZero}
                fitWidth={this._props.childLayoutFitWidth}
                hideFilterStatus={true}
                showTags={BoolCast(this.layoutDoc.showChildTags)}
                containerViewPath={this.childContainerViewPath}
                setContentViewBox={undefined}
                onDoubleClickScript={this.onContentDoubleClick}
                onClickScript={this.onContentClick}
                isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
                isContentActive={this.isChildContentActive}
                hideCaptions={showCaptions}
                renderDepth={this._props.renderDepth + 1}
                LayoutTemplate={this._props.childLayoutTemplate}
                LayoutTemplateString={this._props.childLayoutString}
                TemplateDataDocument={DocCast(Doc.Layout(doc).rootDocument)?.[DocData]}
                childFilters={this.childDocFilters}
                focus={this.focus}
                hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
                addDocument={this._props.addDocument}
                ScreenToLocalTransform={this.contentScreenToLocalXf}
                PanelWidth={this.contentPanelWidth}
                PanelHeight={this.contentPanelHeight}
                screenXPadding={this.screenXPadding}
            />
        );
    };
    /**
     * Display an overlay of the previous card that crossfades to the next card
     */
    @computed get overlay() {
        const fadeTime = 500;
        const lastDoc = this.carouselItems?.[this._last_index]?.layout;
        return !lastDoc || this.carouselIndex === this._last_index ? null : (
            <div className="collectionCarouselView-image" style={{ opacity: this._last_opacity, transition: `opacity ${fadeTime}ms` }}>
                {this.renderDoc(
                    lastDoc,
                    false, // hide captions if the carousel is configured to show the captions
                    action((r: DocumentView | null) => {
                        if (r) {
                            this._fadeTimer && clearTimeout(this._fadeTimer);
                            this._last_opacity = 0;
                            this._fadeTimer = setTimeout(
                                action(() => {
                                    this._last_index = -1;
                                    this._last_opacity = 1;
                                }),
                                fadeTime
                            );
                        }
                    })
                )}
            </div>
        );
    }
    @computed get renderedDoc() {
        const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
        return this.renderDoc(this.curDoc(), !!carouselShowsCaptions);
    }

    @computed get content() {
        const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
        const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
        return !this.curDoc() ? null : (
            <>
                <div className="collectionCarouselView-image" key="image">
                    {this.renderedDoc}
                    {this.overlay}
                </div>
                {!carouselShowsCaptions ? null : (
                    <div
                        className="collectionCarouselView-caption"
                        key="caption"
                        onWheel={StopEvent}
                        style={{
                            borderRadius: this._props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding) as string,
                            marginRight: this.captionMarginX,
                            marginLeft: this.captionMarginX,
                            width: `calc(100% - ${this.captionMarginX * 2}px)`,
                        }}>
                        <FormattedTextBox xPadding={10} yPadding={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={this.curDoc()} TemplateDataDocument={undefined} />
                    </div>
                )}
            </>
        );
    }

    @computed get navButtons() {
        return !this.curDoc() ? null : (
            <>
                <div key="back" className="carouselView-back" style={{ transform: `scale(${this.uiBtnScaling})` }} onClick={this.goback}>
                    <FontAwesomeIcon icon="chevron-left" size="2x" />
                </div>
                <div key="fwd" className="carouselView-fwd" style={{ transform: `scale(${this.uiBtnScaling})` }} onClick={this.advance}>
                    <FontAwesomeIcon icon="chevron-right" size="2x" />
                </div>
            </>
        );
    }

    nativeScaling = () => this._props.NativeDimScaling?.() || 1;

    docViewProps = () => ({
        ...this._props, //
        isDocumentActive: this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive,
        isContentActive: this.isChildContentActive,
        ScreenToLocalTransform: this.contentScreenToLocalXf,
    });
    answered = (correct: boolean) => (!correct || !this.curDoc() || NumCast(this.layoutDoc._carousel_index) === this.carouselItems.length - 1) && this.advance();

    render() {
        return (
            <div>
                <div
                    className="collectionCarouselView-outer"
                    ref={this.createDashEventsTarget}
                    style={{
                        background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
                        color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string,
                        left: NumCast(this.layoutDoc._xMargin),
                        top: NumCast(this.layoutDoc._yMargin),
                        transformOrigin: 'top left',
                        transform: `scale(${this.nativeScaling()})`,
                        width: `calc(${100 / this.nativeScaling()}% - ${(2 * NumCast(this.layoutDoc._xMargin)) / this.nativeScaling()}px)`,
                        height: `calc(${100 / this.nativeScaling()}% - ${(2 * NumCast(this.layoutDoc._yMargin)) / this.nativeScaling()}px)`,
                        position: 'relative',
                    }}>
                    {this.content}
                    {this.navButtons}
                </div>
                {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)}
            </div>
        );
    }
}
