import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
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, 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 { StyleProp } from '../StyleProp';
import { TagItem } from '../TagsView';
import { DocumentView } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import './CollectionCarouselView.scss';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';

enum cardMode {
    STAR = 'star',
    ALL = 'all',
}
enum practiceMode {
    PRACTICE = 'practice',
    QUIZ = 'quiz',
}
enum practiceVal {
    MISSED = 'missed',
    CORRECT = 'correct',
}
@observer
export class CollectionCarouselView extends CollectionSubView() {
    private _dropDisposer?: DragManager.DragDropDisposer;
    get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore
    get sideField()     { return "_" + this.fieldKey + "_usePath"; } // prettier-ignore
    get starField()     { return "#star"; } // prettier-ignore

    _fadeTimer: NodeJS.Timeout | undefined;

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

    @observable _last_index = this.carouselIndex;
    @observable _last_opacity = 1;

    componentWillUnmount() {
        this._dropDisposer?.();
    }

    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
        this._dropDisposer?.();
        if (ele) {
            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
        }
    };

    @computed get practiceMode() {
        return this.childDocs.some(doc => doc._layout_isFlashcard) ? StrCast(this.layoutDoc.practiceMode) : '';
    }
    @computed get practiceMessage() {
        const cardCount = this.carouselItems.length;
        if (this.practiceMode) {
            if (!Doc.hasDocFilter(this.layoutDoc, 'tags', Doc.FilterAny) && !cardCount) {
                return 'Finished! Click here to view all flashcards.';
            }
        }
        return '';
    }

    @computed get filterMessage() {
        const cardCount = this.carouselItems.length;
        if (!this.practiceMessage) {
            if (Doc.hasDocFilter(this.layoutDoc, 'tags', Doc.FilterAny) && !cardCount) {
                return 'No tagged items. Click here to view all flash cards.';
            }
            if (this.practiceMode) {
                if (!cardCount) return 'No flashcards to show! Click here to leave practice mode';
            }
        }
        return '';
    }
    @computed get marginX()       { 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.childDocs
        .filter(doc => doc.type !== DocumentType.LINK)
        .filter(doc => !this.practiceMode || (BoolCast(doc?._layout_isFlashcard) && doc[this.practiceField] !== practiceVal.CORRECT))// show only cards that aren't marked as correct
    } // 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 = (e: React.MouseEvent) => {
        e.stopPropagation();
        this.move(1);
    };

    /**
     * Goes to the previous Doc in the stack subject to the currently selected filter option.
     */
    goback = (e: React.MouseEvent) => {
        e.stopPropagation();
        this.move(-1);
    };

    /*
     * Toggles whether the 'star' metadata field is set on the current Doc
     */
    toggleStar = (e: React.MouseEvent) => {
        e.stopPropagation();
        const curDoc = this.carouselItems[this.carouselIndex];
        if (curDoc) {
            if (TagItem.docHasTag(curDoc, this.starField)) TagItem.removeTagFromDoc(curDoc, this.starField);
            else TagItem.addTagToDoc(curDoc, this.starField);
        }
    };

    /*
     * Sets a flashcard to either missed or correct depending on if they got the question right in practice mode.
     */
    setPracticeVal = (e: React.MouseEvent, val: string) => {
        e.stopPropagation();
        const curDoc = this.carouselItems[this.carouselIndex];
        curDoc && (curDoc[this.practiceField] = val);
        this.advance(e);
    };

    /**
     * Sets the practice mode answer style for flashcards
     * @param mode practiceMode or undefined for no practice
     */
    setPracticeMode = (mode: practiceMode | undefined) => {
        this.layoutDoc.practiceMode = mode;
        this.carouselItems?.map(doc => (doc[this.practiceField] = undefined));
        if (mode === practiceMode.QUIZ) this.carouselItems?.map(doc => (doc[this.sideField] = undefined));
    };

    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);
    };
    contentPanelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0) - 2 * NumCast(this.layoutDoc.yMargin);
    onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
    onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
    captionWidth = () => this._props.PanelWidth() - 2 * this.marginX;
    contentScreentToLocalXf = () => this._props.ScreenToLocalTransform().translate(-NumCast(this.layoutDoc.xMargin), -NumCast(this.layoutDoc.yMargin));

    contentPanelWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc.xMargin);

    isChildContentActive = () =>
        this._props.isContentActive?.() === false
            ? false
            : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive))
              ? true
              : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
                ? false
                : undefined;

    childScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);

    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}
                showTags={true}
                hideFilterStatus={true}
                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).resolvedDataDoc)}
                childFilters={this.childDocFilters}
                hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
                addDocument={this._props.addDocument}
                ScreenToLocalTransform={this.contentScreentToLocalXf}
                PanelWidth={this.contentPanelWidth}
                PanelHeight={this.contentPanelHeight}
            />
        );
    };
    /**
     * 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];
        return !lastDoc || this.carouselIndex === this._last_index ? null : (
            <div className="collectionCarouselView-image" style={{ opacity: this._last_opacity, position: 'absolute', top: 0, left: 0, 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 content() {
        const index = this.carouselIndex;
        const curDoc = this.carouselItems?.[index];
        const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
        const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
        return !curDoc ? null : (
            <>
                <div className="collectionCarouselView-image" key="image">
                    {this.renderDoc(curDoc, !!carouselShowsCaptions)}
                    {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.marginX,
                            marginLeft: this.marginX,
                            width: `calc(100% - ${this.marginX * 2}px)`,
                        }}>
                        <FormattedTextBox key={index} xPadding={10} yPadding={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={curDoc} TemplateDataDocument={undefined} />
                    </div>
                )}
            </>
        );
    }

    addFlashcard() {
        const newDoc = Docs.Create.ComparisonDocument('', { _layout_isFlashcard: true, _width: 300, _height: 300 });
        this.addDocument?.(newDoc);
    }

    @computed get buttons() {
        if (!this.carouselItems?.[this.carouselIndex]) return null;
        return (
            <>
                <div>
                    {/* <Tooltip title="add new flashcard to pile">
                        <div key="add" className="carouselView-add" onClick={this.addFlashcard}>
                            <FontAwesomeIcon icon="plus" color={this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.starField] ? 'yellow' : 'gray'} size="1x" />
                        </div>
                    </Tooltip> */}
                </div>
                <div key="back" className="carouselView-back" onClick={this.goback}>
                    <FontAwesomeIcon icon="chevron-left" size="2x" />
                </div>
                <div key="fwd" className="carouselView-fwd" onClick={this.advance}>
                    <FontAwesomeIcon icon="chevron-right" size="2x" />
                </div>
                {this.practiceMode == practiceMode.PRACTICE ? (
                    <div>
                        <Tooltip title="Incorrect. View again later.">
                            <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)}>
                                <FontAwesomeIcon icon="xmark" color="red" size="1x" />
                            </div>
                        </Tooltip>
                        <Tooltip title="Correct">
                            <div key="check" className="carouselView-check" onClick={e => this.setPracticeVal(e, practiceVal.CORRECT)}>
                                <FontAwesomeIcon icon="check" color="green" size="1x" />
                            </div>
                        </Tooltip>
                    </div>
                ) : null}
            </>
        );
    }

    togglePracticeMode = (mode: practiceMode) => this.setPracticeMode(mode === this.practiceMode ? undefined : mode);
    toggleFilterMode = () => Doc.setDocFilter(this.Document, 'tags', this.starField, 'check', true);
    setColor = (mode: practiceMode | cardMode, which: string) => (which === mode ? 'white' : 'light gray');

    @computed get filterDoc() {
        return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter');
    }
    filterHeight = () => NumCast(this.filterDoc?.height);
    filterWidth = () => (!this.filterDoc ? 1 : (this.filterHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height));
    @computed get menu() {
        const curDoc = this.carouselItems?.[this.carouselIndex];
        return (
            <div className="carouselView-menu">
                {!this.filterDoc ? null : (
                    <div style={{ height: this.filterHeight(), width: this.filterHeight() }}>
                        <DocumentView
                            {...this._props}
                            Document={this.filterDoc}
                            TemplateDataDocument={undefined}
                            LayoutTemplate={this._props.childLayoutTemplate}
                            LayoutTemplateString={this._props.childLayoutString}
                            renderDepth={this._props.renderDepth + 1}
                            NativeWidth={returnZero}
                            NativeHeight={returnZero}
                            fitWidth={undefined}
                            showTags={false}
                            hideFilterStatus={true}
                            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={true}
                            childFilters={this.childDocFilters}
                            hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
                            addDocument={this._props.addDocument}
                            ScreenToLocalTransform={this.contentScreentToLocalXf}
                            PanelWidth={this.filterWidth}
                            PanelHeight={this.filterHeight}
                        />
                    </div>
                )}
                <div className="carouselView-practiceModes" style={{ display: BoolCast(curDoc?._layout_isFlashcard) ? undefined : 'none' }}>
                    <Tooltip title="Practice flashcards using GPT">
                        <div key="back" className="carouselView-quiz" onClick={e => this.togglePracticeMode(practiceMode.QUIZ)}>
                            <FontAwesomeIcon icon="file-pen" color={this.setColor(practiceMode.QUIZ, StrCast(this.practiceMode))} size="1x" />
                        </div>
                    </Tooltip>
                    <Tooltip title={this.practiceMode === practiceMode.PRACTICE ? 'Exit practice mode' : 'Practice flashcards manually'}>
                        <div key="back" className="carouselView-practice" onClick={e => this.togglePracticeMode(practiceMode.PRACTICE)}>
                            <FontAwesomeIcon icon="check" color={this.setColor(practiceMode.PRACTICE, StrCast(this.practiceMode))} size="1x" />
                        </div>
                    </Tooltip>
                </div>
            </div>
        );
    }

    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,
                        width: `calc(100% - ${NumCast(this.layoutDoc._xMargin)}px)`,
                        height: `calc(100% - ${NumCast(this.layoutDoc._yMargin)}px)`,
                        left: NumCast(this.layoutDoc._xMargin),
                        top: NumCast(this.layoutDoc._yMargin),
                    }}>
                    {!this.practiceMessage && !this.filterMessage ? (
                        this.content
                    ) : (
                        <p
                            className="message"
                            onClick={() => {
                                if (this.filterMessage || this.practiceMessage) {
                                    this.setPracticeMode(undefined);
                                    Doc.setDocFilter(this.layoutDoc, 'tags', Doc.FilterAny, 'remove');
                                }
                            }}>
                            {this.filterMessage || this.practiceMessage}
                        </p>
                    )}
                </div>
                {!this.Document._chromeHidden ? this.menu : null}
                {!this.Document._chromeHidden ? this.buttons : null}
            </div>
        );
    }
}
