import { action, computed, makeObservable, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { ObservableReactComponent } from "../../../../ObservableReactComponent";
import { DataVizTemplateLayout, DocCreatorMenu, LayoutType } from "../DocCreatorMenu";
import React from "react";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { setupMoveUpEvents, returnFalse, returnEmptyFilter } from "../../../../../../ClientUtils";
import { emptyFunction } from "../../../../../../Utils";
import { undoable } from "../../../../../util/UndoManager";
import ReactLoading from "react-loading";
import { Doc, NumListCast, returnEmptyDoclist } from "../../../../../../fields/Doc";
import { StrCast } from "../../../../../../fields/Types";
import { DefaultStyleProvider } from "../../../../StyleProvider";
import { DocumentView } from "../../../DocumentView";
import { Transform } from "../../../../../util/Transform";
import { Docs, DocumentOptions } from "../../../../../documents/Documents";
import { Template } from "../Template";

interface TemplatesRenderPreviewWindowProps {
    menu: DocCreatorMenu;
}

@observer
export class TemplatesRenderPreviewWindow extends ObservableReactComponent<TemplatesRenderPreviewWindowProps> {

    @observable private _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 0, repeat: 0 };

    @observable private renderedDocs: Doc[] = [];
    @observable private renderedDocCollection: Doc | undefined = undefined;

    @observable private loading: boolean = false;

    constructor(props: TemplatesRenderPreviewWindowProps) {
        super(props);
        makeObservable(this);
        this.updateRenderedPreviewCollection();
    }

    @computed get canMakeDocs() {
        return this._props.menu._selectedTemplate !== undefined && this._layout !== undefined;
    }

    @computed get docsToRender() {
        if (this._props.menu.DEBUG_MODE) {
            return [1, 2, 3, 4];
        } else {
            return NumListCast(this._props.menu._dataViz?.layoutDoc.dataViz_selectedRows);
        }
    }

    @computed get rowsCount() {
        switch (this._layout.type) {
            case LayoutType.FREEFORM:
                return Math.ceil(this.docsToRender.length / (this._layout.columns ?? 1)) ?? 0;
            case LayoutType.CAROUSEL3D:
                return 1.8;
            default:
                return 1;
        }
    }

    @computed get columnsCount() {
        switch (this._layout.type) {
            case LayoutType.FREEFORM:
                return this._layout.columns ?? 0;
            case LayoutType.CAROUSEL3D:
                return 3;
            default:
                return 1;
        }
    }

    @action updateRenderedPreviewCollection = async () => {
        this.loading = true;
        this.renderedDocs = await this._props.menu.createDocsForPreview();
        this.updateRenderedDocCollection();
    };

    /**
     * Updates the preview that shows how all docs will be rendered in the chosen collection type.
    @type the type of collection the docs should render to (ie. freeform, carousel, card)
     */
    updateRenderedDocCollection = () => {
        if (!this.renderedDocs) return;

        const collectionFactory = (): ((docs: Doc[], options: DocumentOptions) => Doc) => {
            switch (this._layout.type) {
                case LayoutType.CAROUSEL3D: return Docs.Create.Carousel3DDocument;
                case LayoutType.FREEFORM:   return Docs.Create.FreeformDocument;
                case LayoutType.CARD:       return Docs.Create.CardDeckDocument;
                case LayoutType.MASONRY:    return Docs.Create.MasonryDocument;
                case LayoutType.CAROUSEL:   return Docs.Create.CarouselDocument;
                default:                    return Docs.Create.FreeformDocument;
            } // prettier-ignore
        };

        const collection = collectionFactory()(this.renderedDocs, {
            isDefaultTemplateDoc: true,
            title: 'title',
            backgroundColor: 'gray',
            x: 200,
            y: 200,
            _width: 4000,
            _height: 4000,
        });

        this.applyLayout(collection, this.renderedDocs);

        this.renderedDocCollection = collection;

        this.loading = false;

        this.forceUpdate();
    };

    @action updateMargin = (input: string, xOrY: 'x' | 'y') => {
        this._layout[`${xOrY}Margin`] = Number(input);
        setTimeout(() => {
            if (!this.renderedDocCollection || !this.renderedDocs) return;
            this.applyLayout(this.renderedDocCollection, this.renderedDocs);
        });
    };

    @action updateColumns = (input: string) => {
        this._layout.columns = Number(input);
        this.updateRenderedDocCollection();
    };

    applyLayout = (collection: Doc, docs: Doc[]) => {
        const { horizontalSpan, verticalSpan } = this.previewInfo;
        collection._height = verticalSpan;
        collection._width = horizontalSpan;

        const columns: number = this._layout.columns ?? this.columnsCount;
        const xGap: number = this._layout.xMargin;
        const yGap: number = this._layout.yMargin;
        const startX: number = -Number(collection._width) / 2;
        const startY: number = -Number(collection._height) / 2;
        const docHeight: number = Number(docs[0]._height);
        const docWidth: number = Number(docs[0]._width);

        if (columns === 0 || docs.length === 0) {
            return;
        }

        let i: number = 0;
        let docsChanged: number = 0;
        let curX: number = startX;
        let curY: number = startY;

        while (docsChanged < docs.length) {
            while (i < columns && docsChanged < docs.length) {
                docs[docsChanged].x = curX;
                docs[docsChanged].y = curY;
                curX += docWidth + xGap;
                ++docsChanged;
                ++i;
            }
            i = 0;
            curX = startX;
            curY += docHeight + yGap;
        }
    };

    @computed
    get previewInfo() {
        const docHeight: number = Number(this.renderedDocs[0]._height);
        const docWidth: number = Number(this.renderedDocs[0]._width);
        const layout = this._layout;
        return {
            docHeight: docHeight,
            docWidth: docWidth,
            horizontalSpan: (docWidth + layout.xMargin) * this.columnsCount - layout.xMargin,
            verticalSpan: (docHeight + layout.yMargin) * this.rowsCount - layout.yMargin,
        };
    }

    get layoutConfigOptions() {
        const optionInput = (icon: string, func: (input: string) => void, def?: number, key?: string, noMargin?: boolean) => {
            return (
                <div className="docCreatorMenu-option-container small no-margin" key={key} style={{ marginTop: noMargin ? '0px' : '' }}>
                    <div className="docCreatorMenu-option-title config layout-config">
                        <FontAwesomeIcon icon={icon as IconProp} />
                    </div>
                    <input defaultValue={def} onInput={e => func(e.currentTarget.value)} className="docCreatorMenu-input config layout-config" />
                </div>
            );
        };

        switch (this._layout.type) {
            case LayoutType.FREEFORM:
                return (
                    <div className="docCreatorMenu-configuration-bar">
                        {optionInput('arrows-up-down', (input: string) => this.updateMargin(input, 'y'), this._layout.xMargin, '2')}
                        {optionInput('arrows-left-right', (input: string) => this.updateMargin(input, 'x'), this._layout.xMargin, '3')}
                        {optionInput('table-columns', this.updateColumns, this._layout.columns, '4', true)}
                    </div>
                );
            default:
                break;
        }
    }

    layoutPreviewContents = action(() => {
        return this.loading ? (
            <div className="docCreatorMenu-layout-preview-window-wrapper loading">
                <div className="loading-spinner">
                    <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
                </div>
            </div>
        ) : !this.renderedDocCollection ? null : (
            <div className="docCreatorMenu-layout-preview-window-wrapper">
                <DocumentView
                    Document={this.renderedDocCollection}
                    isContentActive={emptyFunction}
                    addDocument={returnFalse}
                    moveDocument={returnFalse}
                    removeDocument={returnFalse}
                    PanelWidth={() => this._props.menu._menuDimensions.width - 80}
                    PanelHeight={() => this._props.menu._menuDimensions.height - 105}
                    ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)}
                    renderDepth={5}
                    whenChildContentsActiveChanged={emptyFunction}
                    focus={emptyFunction}
                    styleProvider={DefaultStyleProvider}
                    addDocTab={this._props.menu._props.addDocTab}
                    pinToPres={() => undefined}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                    fitContentsToBox={returnFalse}
                    fitWidth={returnFalse}
                    hideDecorations={true}
                />
            </div>
        );
    });

    selectionBox = (width: number, height: number, icon: string, specClass?: string, options?: JSX.Element[], manual?: boolean): JSX.Element => {
        return (
            <div className="docCreatorMenu-option-container">
                <div className={`docCreatorMenu-option-title config ${specClass}`} style={{ width: width * 0.4, height: height }}>
                    <FontAwesomeIcon icon={icon as IconProp} />
                </div>
                {manual ? (
                    <input className={`docCreatorMenu-input config ${specClass}`} style={{ width: width * 0.6, height: height }} />
                ) : (
                    <select className={`docCreatorMenu-input config ${specClass}`} style={{ width: width * 0.6, height: height }}>
                        {options}
                    </select>
                )}
            </div>
        );
    };

    layoutOption = (option: LayoutType, optStyle?: object, specialFunc?: () => void) => {
        return (
            <div
                className="docCreatorMenu-dropdown-option"
                style={optStyle}
                onPointerDown={e =>
                    this._props.menu.setUpButtonClick(e, () => {
                        specialFunc?.();
                        runInAction(() => {
                            this._layout.type = option;
                            this.updateRenderedDocCollection();
                        });
                    })
                }>
                {option}
            </div>
        );
    };

    get optionsMenuContents() {

        const repeatOptions = [0, 1, 2, 3, 4, 5];

        return (
            <div className="docCreatorMenu-menu-container">
                <div className="docCreatorMenu-option-container layout">
                    <div className="docCreatorMenu-dropdown-hoverable">
                        <div className="docCreatorMenu-option-title">{this._layout.type ? this._layout.type.toUpperCase() : 'Choose Layout'}</div>
                        <div className="docCreatorMenu-dropdown-content">
                            {this.layoutOption(LayoutType.FREEFORM, undefined, () => {
                                if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length));
                            })}
                            {this.layoutOption(LayoutType.CAROUSEL)}
                            {this.layoutOption(LayoutType.CAROUSEL3D)}
                            {this.layoutOption(LayoutType.MASONRY)}
                        </div>
                    </div>
                </div>
                {this._layout.type ? this.layoutConfigOptions : null}
                {this.layoutPreviewContents()}
                {this.selectionBox(
                    60,
                    20,
                    'repeat',
                    undefined,
                    repeatOptions.map(num => <option key={num} onPointerDown={() => (this._layout.repeat = num)}>{`${num}x`}</option>)
                )}
                <hr className="docCreatorMenu-option-divider" />
                <div className="docCreatorMenu-general-options-container">
                    <button
                        className="docCreatorMenu-save-layout-button"
                        onPointerDown={e =>
                            setupMoveUpEvents(
                                this,
                                e,
                                returnFalse,
                                emptyFunction,
                                undoable(clickEv => {
                                    clickEv.stopPropagation();
                                    //previous implementation deprecated; return later to add or scrap
                                    return;
                                }, 'save layout')
                            )
                        }>
                        <FontAwesomeIcon icon="floppy-disk" />
                    </button>
                    <button
                        className="docCreatorMenu-create-docs-button"
                        style={{ backgroundColor: this.canMakeDocs ? '' : 'rgb(155, 155, 155)', border: this.canMakeDocs ? '' : 'solid 2px rgb(180, 180, 180)' }}
                        onPointerDown={e =>
                            setupMoveUpEvents(
                                this,
                                e,
                                returnFalse,
                                emptyFunction,
                                undoable(clickEv => {
                                    clickEv.stopPropagation();
                                    this.renderedDocCollection && this._props.menu.addRenderedCollectionToMainview(this.renderedDocCollection);
                                }, 'make docs')
                            )
                        }>
                        <FontAwesomeIcon icon="plus" />
                    </button>
                </div>
            </div>
        );
    }

    render() { return this.optionsMenuContents }
}