import { Colors } from '@dash/components';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { IDisposer } from 'mobx-utils';
import * as React from 'react';
import ReactLoading from 'react-loading';
import { ClientUtils, returnEmptyFilter, returnFalse, setupMoveUpEvents } from '../../../../../ClientUtils';
import { emptyFunction } from '../../../../../Utils';
import { Doc, NumListCast, StrListCast, returnEmptyDoclist } from '../../../../../fields/Doc';
import { Id } from '../../../../../fields/FieldSymbols';
import { ImageCast, StrCast } from '../../../../../fields/Types';
import { ImageField } from '../../../../../fields/URLField';
import { Upload } from '../../../../../server/SharedMediaTypes';
import { Networking } from '../../../../Network';
import { GPTCallType, gptAPICall, gptImageCall } from '../../../../apis/gpt/GPT';
import { Docs, DocumentOptions } from '../../../../documents/Documents';
import { DragManager } from '../../../../util/DragManager';
import { SnappingManager } from '../../../../util/SnappingManager';
import { Transform } from '../../../../util/Transform';
import { UndoManager, undoable } from '../../../../util/UndoManager';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
import { DefaultStyleProvider } from '../../../StyleProvider';
import { CollectionFreeFormView } from '../../../collections/collectionFreeForm/CollectionFreeFormView';
import { DocumentView, DocumentViewInternal } from '../../DocumentView';
import { OpenWhere } from '../../OpenWhere';
import { DataVizBox } from '../DataVizBox';
import './DocCreatorMenu.scss';
import { TemplateField, ViewType } from './TemplateFieldTypes/TemplateField';
import { Template } from './Template';
import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend';
import { Conditional, TemplateManager } from './Backend/TemplateManager';
import { DrawingFillHandler } from '../../../smartdraw/DrawingFillHandler';
import { CgPathIntersect } from 'react-icons/cg';
import { StaticContentField } from './TemplateFieldTypes/StaticContentField';
import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils'
import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid';
import { FireflyStructureOptions, TemplateEditingWindow } from './Menu/TemplateEditingWindow';
import { DocCreatorMenuButton } from './Menu/DocCreatorMenuButton';
import { ConditionalsTextArea } from './Menu/ConditionalsTextarea';

export enum LayoutType {
    FREEFORM = 'Freeform',
    CAROUSEL = 'Carousel',
    CAROUSEL3D = '3D Carousel',
    MASONRY = 'Masonry',
    CARD = 'Card View',
}

export interface DataVizTemplateInfo {
    doc: Doc;
    layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number };
    columns: number;
    referencePos: { x: number; y: number };
}

export interface DataVizTemplateLayout {
    template: Doc;
    docsNumList: number[];
    layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number };
    columns: number;
    rows: number;
}

export type Col = {
    sizes: TemplateFieldSize[];
    desc: string;
    title: string;
    type: TemplateFieldType;
    defaultContent?: string;
    AIGenerated?: boolean;
};

interface DocCreateMenuProps {
    addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
}

@observer
export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> {
    // eslint-disable-next-line no-use-before-define
    static Instance: DocCreatorMenu;

    private DEBUG_MODE: boolean = false;
    private _ref: HTMLDivElement | null = null;
    private templateManager: TemplateManager;

    @observable _fullyRenderedDocs: Doc[] = []; // collection of templates filled in with content
    @observable _renderedDocCollection: Doc | undefined = undefined; // fullyRenderedDocs in a parent collection
    @observable _docsRendering: boolean = false; // dictates loading symbol

    @observable _userTemplates: Template[] = [];
    @observable _selectedTemplate: Template | undefined = undefined;
    @observable _currEditingTemplate: Template | undefined = undefined;
    @observable _editedTemplateTrail: Template[] = [];

    @observable _userCreatedFields: Col[] = [];
    @observable _collapsedCols: String[] = []; //any columns whose options panels are hidden
    @observable _currEditingConditional: Conditional = {} as Conditional;

    @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 3, repeat: 0 };
    @observable _savedLayouts: DataVizTemplateLayout[] = [];
    @observable _loadingVariants: boolean = false;

    @observable _suggestedTemplates: Template[] = [];
    @observable _GPTLoading: boolean = false;

    @observable _pageX: number = 0;
    @observable _pageY: number = 0;

    @observable _hoveredLayoutPreview: number | undefined = undefined;
    @observable _mouseX: number = -1;
    @observable _mouseY: number = -1;
    @observable _startPos?: { x: number; y: number };
    @observable _shouldDisplay: boolean = false;

    @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' | 'templateEditing' = 'templates';
    @observable _dragging: boolean = false;
    @observable _dataViz?: DataVizBox;
    @observable _interactionLock: boolean | undefined;
    @observable _snapPt: { x: number; y: number } = { x: 0, y: 0 };
    @observable _resizeHdlId: string = '';
    @observable _resizing: boolean = false;
    @observable _offset: { x: number; y: number } = { x: 0, y: 0 };
    @observable _resizeUndo: UndoManager.Batch | undefined = undefined;
    @observable _initDimensions: { width: number; height: number; x?: number; y?: number } = { width: 300, height: 400, x: undefined, y: undefined };
    @observable _menuDimensions: { width: number; height: number } = { width: 400, height: 400 };

    @observable _variations: Template[] = [];

    constructor(props: DocCreateMenuProps) {
        super(props);
        makeObservable(this);
        DocCreatorMenu.Instance = this;
        this.templateManager = new TemplateManager(TemplateLayouts.allTemplates);
    }

    @action setDataViz = (dataViz: DataVizBox) => {
        this._dataViz = dataViz;
        this._selectedTemplate = undefined;
        this._renderedDocCollection = undefined;
        this._fullyRenderedDocs = [];
        this._suggestedTemplates = [];
        this._userCreatedFields = [];
    };
    @action setSuggestedTemplates = (templates: Template[]) => {
        this._suggestedTemplates = templates; //prettier-ignore
    };

    @computed get docsToRender() {
        if (this.DEBUG_MODE) {
            return [1, 2, 3, 4];
        } else {
            return this._selectedTemplate ? NumListCast(this._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;
        }
    }

    @computed get selectedFields() {
        return StrListCast(this._dataViz?.layoutDoc._dataViz_axes);
    }

    @computed get fieldsInfos(): Col[] {
        const colInfo = this._dataViz?.colsInfo;
        return this.selectedFields
            .map(field => {
                const fieldInfo = colInfo?.get(field);

                const col: Col = {
                    title: field,
                    type: fieldInfo?.type ?? TemplateFieldType.UNSET,
                    desc: fieldInfo?.desc ?? '',
                    sizes: fieldInfo?.sizes ?? [TemplateFieldSize.MEDIUM],
                };

                if (fieldInfo?.defaultContent !== undefined) {
                    col.defaultContent = fieldInfo.defaultContent;
                }

                return col;
            })
            .concat(this._userCreatedFields);
    }

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

    get bounds(): { t: number; b: number; l: number; r: number } {
        const rect = this._ref?.getBoundingClientRect();
        const bounds = { t: rect?.top ?? 0, b: rect?.bottom ?? 0, l: rect?.left ?? 0, r: rect?.right ?? 0 };
        return bounds;
    }

    setUpButtonClick = (e: React.PointerEvent, func: (...args: any) => void) => {
        setupMoveUpEvents(
            this,
            e,
            returnFalse,
            emptyFunction,
            undoable(clickEv => {
                clickEv.stopPropagation();
                clickEv.preventDefault();
                func();
            }, 'create docs')
        );
    };

    @action
    onPointerDown = (e: PointerEvent) => {
        this._mouseX = e.clientX;
        this._mouseY = e.clientY;
    };

    @action
    onPointerUp = (e: PointerEvent) => {
        if (this._resizing) {
            this._initDimensions.width = this._menuDimensions.width;
            this._initDimensions.height = this._menuDimensions.height;
            this._initDimensions.x = this._pageX;
            this._initDimensions.y = this._pageY;
            document.removeEventListener('pointermove', this.onResize);
            SnappingManager.SetIsResizing(undefined);
            this._resizing = false;
        }
        if (this._dragging) {
            document.removeEventListener('pointermove', this.onDrag);
            this._dragging = false;
        }
        if (e.button !== 2 && !e.ctrlKey) return;
        const curX = e.clientX;
        const curY = e.clientY;
        if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) {
            this._shouldDisplay = false;
        }
    };

    componentDidMount() {
        document.addEventListener('pointerdown', this.onPointerDown, true);
        document.addEventListener('pointerup', this.onPointerUp);
    }

    componentWillUnmount() {
        document.removeEventListener('pointerdown', this.onPointerDown, true);
        document.removeEventListener('pointerup', this.onPointerUp);
    }

    @action
    toggleDisplay = (x: number, y: number) => {
        if (this._shouldDisplay) {
            this._shouldDisplay = false;
        } else {
            this._pageX = x;
            this._pageY = y;
            this._shouldDisplay = true;
        }
    };

    @action
    closeMenu = () => {
        this._shouldDisplay = false;
    };

    @action
    openMenu = () => {
        this._shouldDisplay = true;
    };

    @action
    onResizePointerDown = (e: React.PointerEvent): void => {
        this._resizing = true;
        document.addEventListener('pointermove', this.onResize);
        SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
        e.stopPropagation();
        const id = (this._resizeHdlId = e.currentTarget.className);
        const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as HTMLElement).width.replace('px', '')) / 2 : 0;
        const bounds = e.currentTarget.getBoundingClientRect();
        this._offset = {
            x: id.toLowerCase().includes('left') ? bounds.right - e.clientX - pad : bounds.left - e.clientX + pad, //
            y: id.toLowerCase().includes('top') ? bounds.bottom - e.clientY - pad : bounds.top - e.clientY + pad,
        };
        this._resizeUndo = UndoManager.StartBatch('drag resizing');
        this._snapPt = { x: e.pageX, y: e.pageY };
    };

    @action
    onResize = (e: PointerEvent): boolean => {
        const dragHdl = this._resizeHdlId.split(' ')[1];
        const thisPt = DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y);

        const { scale, refPt, transl } = this.getResizeVals(thisPt, dragHdl);
        !this._interactionLock && runInAction(async () => {   // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
            this._interactionLock = true;
            const scaleAspect = {x: scale.x, y: scale.y};
            this.resizeView(refPt, scaleAspect, transl); // prettier-ignore
            await new Promise<boolean | undefined>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
        }); // prettier-ignore
        return true;
    };

    @action
    onDrag = (e: PointerEvent): boolean => {
        this._pageX = e.pageX - (this._startPos?.x ?? 0);
        this._pageY = e.pageY - (this._startPos?.y ?? 0);
        this._initDimensions.x = this._pageX;
        this._initDimensions.y = this._pageY;
        return true;
    };

    getResizeVals = (thisPt: { x: number; y: number }, dragHdl: string) => {
        const [w, h] = [this._initDimensions.width, this._initDimensions.height];
        const [moveX, moveY] = [thisPt.x - this._snapPt!.x, thisPt.y - this._snapPt!.y];
        let vals: { scale: { x: number; y: number }; refPt: [number, number]; transl: { x: number; y: number } };
        switch (dragHdl) {
            case 'topLeft':    vals = { scale: { x: 1 - moveX / w, y: 1 -moveY / h },  refPt: [this.bounds.r, this.bounds.b], transl: {x: moveX, y: moveY } }; break;
            case 'topRight':   vals = { scale: { x: 1 + moveX / w, y: 1 -moveY / h },  refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break;
            case 'top':        vals = { scale: { x: 1,             y: 1 -moveY / h },  refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break;
            case 'left':       vals = { scale: { x: 1 - moveX / w, y: 1 },             refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break;
            case 'bottomLeft': vals = { scale: { x: 1 - moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break;
            case 'right':      vals = { scale: { x: 1 + moveX / w, y: 1 },             refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
            case 'bottomRight':vals = { scale: { x: 1 + moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
            case 'bottom':     vals = { scale: { x: 1,             y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
            default:           vals = { scale: { x: 1,             y: 1 },             refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
        } // prettier-ignore
        return vals;
    };

    resizeView = (refPt: number[], scale: { x: number; y: number }, translation: { x: number; y: number }) => {
        if (this._initDimensions.x === undefined) this._initDimensions.x = this._pageX;
        if (this._initDimensions.y === undefined) this._initDimensions.y = this._pageY;
        const { height, width, x, y } = this._initDimensions;

        this._menuDimensions.width = Math.max(300, scale.x * width);
        this._menuDimensions.height = Math.max(200, scale.y * height);
        this._pageX = x + translation.x;
        this._pageY = y + translation.y;
    };

    @action updateRenderedPreviewCollection = async (template: Template) => {
        this._docsRendering = true;
        this._fullyRenderedDocs = this._dataViz ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, template, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : [];
        this.updateRenderedDocCollection();
    };

    @action updateSelectedTemplate = async (template: Template) => {
        if (this._selectedTemplate === template) {
            this._selectedTemplate = undefined;
            return;
        } else {
            this._selectedTemplate = template;
            this.updateRenderedPreviewCollection(template);
        }
    };

    @action updateSelectedSavedLayout = (layout: DataVizTemplateLayout) => {
        this._layout.xMargin = layout.layout.xMargin;
        this._layout.yMargin = layout.layout.yMargin;
        this._layout.type = layout.layout.type;
        this._layout.columns = layout.columns;
    };

    isSelectedLayout = (layout: DataVizTemplateLayout) => {
        return this._layout.xMargin === layout.layout.xMargin && this._layout.yMargin === layout.layout.yMargin && this._layout.type === layout.layout.type && this._layout.columns === layout.columns;
    };

    editTemplate = (doc: Doc) => {
        DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight);
        DocumentView.DeselectAll();
        Doc.UnBrushDoc(doc);
    };

    testTemplate = async () => {
        this._suggestedTemplates = this.templateManager.templates; //prettier-ignore

        //console.log(this.templateManager.templates)

        // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;

        // this.templateManager.templates.forEach(template => {
        //     const doc = template.mainField.renderedDoc();
        //     mainCollection.addDocument(doc);
        // })

        // this.forceUpdate();

        // try {
        //     const res = await gptImageCall('Image of panda eating a cookie');

        //     if (res) {
        //         const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res });

        //         console.log(result);
        //     }
        // } catch (e) {
        //     console.log(e);
        // }
    };

    @action addField = () => {
        const newFields: Col[] = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [], AIGenerated: true }]);
        this._userCreatedFields = newFields;
    };

    @action removeField = (field: { title: string; type: string; desc: string }) => {
        if (this._dataViz?.axes.includes(field.title)) {
            this._dataViz.selectAxes(this._dataViz.axes.filter(col => col !== field.title));
        } else {
            const toRemove = this._userCreatedFields.filter(f => f === field);
            if (!toRemove) return;

            if (toRemove.length > 1) {
                while (toRemove.length > 1) {
                    toRemove.pop();
                }
            }

            if (this._userCreatedFields.length === 1) {
                this._userCreatedFields = [];
            } else {
                this._userCreatedFields.splice(this._userCreatedFields.indexOf(toRemove[0]), 1);
            }
        }
    };

    @action setColTitle = (column: Col, title: string) => {
        if (this.selectedFields.includes(column.title)) {
            this._dataViz?.setColumnTitle(column.title, title);
        } else {
            column.title = title;
        }
        this.forceUpdate();
    };

    @action setColType = (column: Col, type: TemplateFieldType) => {
        if (type === TemplateFieldType.DATA) {
            this.templateManager.addDataField(column.title);
        } else if (column.type === TemplateFieldType.DATA) {
            this.templateManager.removeDataField(column.title);
        }

        if (this.selectedFields.includes(column.title)) {
            this._dataViz?.setColumnType(column.title, type);
        } else {
            column.type = type;
        }

        this.forceUpdate();
    };

    modifyColSizes = (column: Col, size: TemplateFieldSize, valid: boolean) => {
        if (this.selectedFields.includes(column.title)) {
            this._dataViz?.modifyColumnSizes(column.title, size, valid);
        } else {
            if (!valid && column.sizes.includes(size)) {
                column.sizes.splice(column.sizes.indexOf(size), 1);
            } else if (valid && !column.sizes.includes(size)) {
                column.sizes.push(size);
            }
        }
        this.forceUpdate();
    };

    setColDesc = (column: Col, desc: string) => {
        if (this.selectedFields.includes(column.title)) {
            this._dataViz?.setColumnDesc(column.title, desc);
        } else {
            column.desc = desc;
        }
        this.forceUpdate();
    };

    compileFieldDescriptions = (templates: Template[]): string => {
        let descriptions: string = '';
        templates.forEach(template => {
            descriptions += `----------  NEW TEMPLATE TO INCLUDE: The title is: ${template.title}. Its fields are: `;
            descriptions += template.descriptionSummary;
        });

        return descriptions;
    };

    compileColDescriptions = (cols: Col[]): string => {
        let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:';
        cols.forEach(col => (descriptions += `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `));

        return descriptions;
    };

    getColByTitle = (title: string) => {
        return this.fieldsInfos.filter(col => col.title === title)[0];
    };

    @action
    assignColsToFields = async (templates: Template[], cols: Col[]): Promise<[Template, { [field: number]: Col }][]> => {
        const fieldDescriptions: string = this.compileFieldDescriptions(templates);
        const colDescriptions: string = this.compileColDescriptions(cols);

        const inputText = fieldDescriptions.concat(colDescriptions);

        const prompt: string = `(${Math.random() * 100000}) ${inputText}`;

        this._GPTLoading = true;

        try {
            const res = await gptAPICall(prompt, GPTCallType.TEMPLATE);

            if (res) {
                const assignments: { [templateTitle: string]: { [fieldID: string]: string } } = JSON.parse(res);
                const brokenDownAssignments: [Template, { [fieldID: number]: Col }][] = [];

                Object.entries(assignments).forEach(([tempTitle, assignment]) => {
                    const template = templates.filter(temp => temp.title === tempTitle)[0];
                    if (!template) return;
                    const toObj = Object.entries(assignment).reduce(
                        (a, [fieldID, colTitle]) => {
                            const col = this.getColByTitle(colTitle);
                            if (!col.AIGenerated) {
                                var field = template.getFieldByID(Number(fieldID));
                                field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT);
                                field = template.getFieldByID(Number(fieldID));
                                field.setTitle(col.title);
                            } else {
                                a[Number(fieldID)] = this.getColByTitle(colTitle);
                            }
                            return a;
                        },
                        {} as { [field: number]: Col }
                    );
                    brokenDownAssignments.push([template, toObj]);
                });

                return brokenDownAssignments;
            }
        } catch (err) {
            console.error(err);
        }

        return [];
    };

    generatePresetTemplates = async () => {
        const templates: Template[] = [];

        if (this.DEBUG_MODE) {
            templates.push(...this.templateManager.templates);
        } else {
            this._dataViz?.updateColDefaults();

            const contentFields = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA);

            templates.push(...this.templateManager.getValidTemplates(contentFields));

            const assignments = await this.assignColsToFields(templates, contentFields);

            const renderedTemplatePromises = assignments.map(([template, assgns]) => TemplateMenuAIUtils.applyGPTContentToTemplate(template, assgns));

            await Promise.all(renderedTemplatePromises);
        }

        setTimeout(
            action(() => {
                this.setSuggestedTemplates(templates);
                this._GPTLoading = false;
            })
        );
    };

    generateVariations = async (onDoc: Doc, prompt: string, options: FireflyStructureOptions): Promise<string[]> => {
        const { numVariations, temperature, useStyleRef } = options;
        this.variations = [];
        const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;

        const clone: Doc = (await Doc.MakeClone(onDoc)).clone;
        mainCollection.addDocument(clone);
        clone.x = 10000;
        clone.y = 10000;

        await DrawingFillHandler.drawingToImage(clone, 100 - temperature, prompt, useStyleRef ? clone : undefined, this, numVariations)

        return this.variations;
    }

    variations: string[] = []

    @action addVariation = (url: string) => {
        this.variations.push(url);
    }

    addRenderedCollectionToMainview = () => {
        const collection = this._renderedDocCollection;
        if (collection) {
            const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
            collection.x = this._pageX - this._menuDimensions.width;
            collection.y = this._pageY - this._menuDimensions.height;
            mainCollection?.addDocument(collection);
            this.closeMenu();
        }
    };

    @action editLastTemplate = () => { if (this._editedTemplateTrail.length) this._currEditingTemplate = this._editedTemplateTrail.pop()}

    @action setExpandedView = (template: Template | undefined) => {

        if (template) {
            this._menuContent = 'templateEditing';
            this._currEditingTemplate && this._editedTemplateTrail.push(this._currEditingTemplate);
        } else {
            this._menuContent = 'templates';
        }

        this._currEditingTemplate = template;

        //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''});
    };

    @action updateXMargin = (input: string) => {
        this._layout.xMargin = Number(input);
        setTimeout(() => {
            if (!this._renderedDocCollection || !this._fullyRenderedDocs) return;
            this.applyLayout(this._renderedDocCollection, this._fullyRenderedDocs);
        });
    };
    @action updateYMargin = (input: string) => {
        this._layout.yMargin = Number(input);
        setTimeout(() => {
            if (!this._renderedDocCollection || !this._fullyRenderedDocs) return;
            this.applyLayout(this._renderedDocCollection, this._fullyRenderedDocs);
        });
    };
    @action updateColumns = (input: string) => {
        this._layout.columns = Number(input);
        this.updateRenderedDocCollection();
    };

    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', this.updateYMargin, this._layout.xMargin, '2')}
                        {optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '3')}
                        {optionInput('table-columns', this.updateColumns, this._layout.columns, '4', true)}
                    </div>
                );
            default:
                break;
        }
    }

    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._fullyRenderedDocs[0]._height);
        const docWidth: number = Number(this._fullyRenderedDocs[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,
        };
    }

    /**
     * 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._fullyRenderedDocs) 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._fullyRenderedDocs, {
            isDefaultTemplateDoc: true,
            title: 'title',
            backgroundColor: 'gray',
            x: 200,
            y: 200,
            _width: 4000,
            _height: 4000,
        });

        this.applyLayout(collection, this._fullyRenderedDocs);

        this._renderedDocCollection = collection;

        this._docsRendering = false;

        this.forceUpdate();
    };

    layoutPreviewContents = action(() => {
        return this._docsRendering ? (
            <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._menuDimensions.width - 80}
                    PanelHeight={() => this._menuDimensions.height - 105}
                    ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)}
                    renderDepth={5}
                    whenChildContentsActiveChanged={emptyFunction}
                    focus={emptyFunction}
                    styleProvider={DefaultStyleProvider}
                    addDocTab={this._props.addDocTab}
                    pinToPres={() => undefined}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                    fitContentsToBox={returnFalse}
                    fitWidth={returnFalse}
                    hideDecorations={true}
                />
            </div>
        );
    });

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

        const 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>
            );
        };

        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">
                            {layoutOption(LayoutType.FREEFORM, undefined, () => {
                                if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length));
                            })}
                            {layoutOption(LayoutType.CAROUSEL)}
                            {layoutOption(LayoutType.CAROUSEL3D)}
                            {layoutOption(LayoutType.MASONRY)}
                        </div>
                    </div>
                </div>
                {this._layout.type ? this.layoutConfigOptions : null}
                {this.layoutPreviewContents()}
                {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();
                                    if (!this._selectedTemplate || !this._selectedTemplate.getRenderedDoc()) return;
                                    const layout: DataVizTemplateLayout = {
                                        template: this._selectedTemplate.getRenderedDoc()!,
                                        layout: { type: this._layout.type, xMargin: this._layout.xMargin, yMargin: this._layout.yMargin, repeat: 0 },
                                        columns: this.columnsCount,
                                        rows: this.rowsCount,
                                        docsNumList: this.docsToRender,
                                    };
                                    if (!this._savedLayouts.includes(layout)) {
                                        this._savedLayouts.push(layout);
                                    }
                                }, 'make docs')
                            )
                        }>
                        <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();
                                    if (!this._selectedTemplate) return;
                                    this.addRenderedCollectionToMainview();
                                }, 'make docs')
                            )
                        }>
                        <FontAwesomeIcon icon="plus" />
                    </button>
                </div>
            </div>
        );
    }

    @observable private _newCondCache: Record<string, Conditional> = {};

    getParams = (title: string, parameters?: Conditional): Conditional => {
        if (parameters) return parameters;

        if (!this._newCondCache[title]) {
            this._newCondCache[title] = observable<Conditional>({
                field: title,
                operator: '=',
                condition: '',
                target: '',
                attribute: '',
                value: ''
            });
        }
        return this._newCondCache[title];
    };

    get dashboardContents() {
        const contentFieldTitles = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA).map(field => field.title).concat('Template');

        const conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => {

            var params: Conditional = this.getParams(title, parameters);

            return (
            <div className='form'>
                <div className='form-row'>
                    <div className='form-row-plain-text'>If</div>
                    <div className='form-row-plain-text'>{title}</div>
                    <div className="operator-options-dropdown">
                        <span className="operator-dropdown-current">{params.operator ?? '='}</span>
                        <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '='}}>{'='}</div>
                        <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '>'}}>{'>'}</div>
                        <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '<'}}>{'<'}</div>
                        <div className='operator-dropdown-option' onPointerDown={() => {params.operator = 'contains'}}>{'has'}</div>
                    </div>
                    <input
                        className="form-row-textarea"
                        onChange={e => runInAction(() => params.condition = e.target.value)}
                        placeholder='value'
                        value={params.condition}
                    />
                    <div className='form-row-plain-text'>then</div>
                    <div className="operator-options-dropdown">
                        <span className="operator-dropdown-current">{params.target}</span>
                        {contentFieldTitles.map(fieldTitle => 
                            <div className='operator-dropdown-option' onPointerDown={() => {params.target = fieldTitle}}>{fieldTitle === title ? 'Own' : fieldTitle}</div>
                        )}
                    </div>
                    <input
                        className="form-row-textarea"
                        onChange={e => runInAction(() => params.attribute = e.target.value)}
                        placeholder='attribute'
                        value={params.attribute}
                    />
                    <div className='form-row-plain-text'>{'becomes'}</div>
                    <input
                        className="form-row-textarea"
                        onChange={e => runInAction(() => params.value = e.target.value)}
                        placeholder='value'
                        value={params.value}
                    />
                </div>
                {empty ? 
                <DocCreatorMenuButton icon={'plus'} styles={'float-right border'} function={() => {
                    this._newCondCache[title] = observable<Conditional>({
                        field: title,
                        operator: '=',
                        condition: '',
                        target: '',
                        attribute: '',
                        value: ''
                    });
                    this.templateManager.addFieldCondition(title, params);
                }}/>
                :
                <DocCreatorMenuButton icon={'minus'} styles={'float-right border'} function={() => this.templateManager.removeFieldCondition(title, params)}/>
                }
            </div>
            )
        }

        const fieldPanel = (field: Col, id: number) => (
            <div className="field-panel" key={id}>
                <div className="top-bar" onPointerDown={e => this.setUpButtonClick(e, runInAction(() => () => {
                    if (this._collapsedCols.includes(field.title)) {
                        this._collapsedCols = this._collapsedCols.filter(col => col !== field.title);
                    } else {
                        this._collapsedCols.push(field.title);
                    }
                }))}>
                    <span className="field-title">{`${field.title} Field`}</span>
                    <DocCreatorMenuButton icon={'minus'} styles={'no-margin absolute-right'} function={() => this.removeField(field)}/>
                </div>
                { this._collapsedCols.includes(field.title) ? null :
                    <>
                    <div className="opts-bar">
                        <div className="opt-box">
                            <div className="top-bar"> Title </div>
                            <textarea className="content" style={{ width: '100%', height: 'calc(100% - 20px)' }} value={field.title} placeholder={'Enter title'} onChange={e => this.setColTitle(field, e.target.value)} />
                        </div>
                        <div className="opt-box">
                            <div className="top-bar"> Type </div>
                            <div className="content">
                                <span className="type-display">{
                                    field.type === TemplateFieldType.TEXT ? 'Text Field' 
                                    : field.type === TemplateFieldType.VISUAL ? 'File Field' 
                                    : field.type === TemplateFieldType.DATA ? 'Data Field' 
                                    : ''
                                }</span>
                                <div className="bubbles">
                                    <input className="bubble" type="radio" name="type" onClick={() => this.setColType(field, TemplateFieldType.TEXT)} />
                                    <div className="text">Text</div>
                                    <input className="bubble" type="radio" name="type" onClick={() => this.setColType(field, TemplateFieldType.VISUAL)} />
                                    <div className="text">File</div>
                                    <input className="bubble" type="radio" name="type" onClick={() => this.setColType(field, TemplateFieldType.DATA)} />
                                    <div className="text">Data</div>
                                </div>
                            </div>
                        </div>
                    </div>
                    { field.type === TemplateFieldType.DATA ? null :
                        (<> 
                            <div className="sizes-box">
                                <div className="top-bar"> Valid Sizes </div>
                                <div className="content">
                                    <div className="bubbles">
                                        {Object.values(TemplateFieldSize).map(size => (
                                            <div key={field + size}>
                                                <input className="bubble" type="checkbox" name="type" checked={field.sizes.includes(size)} onChange={e => this.modifyColSizes(field, size, e.target.checked)} />
                                                <div className="text">{size}</div>
                                            </div>
                                        ))}
                                    </div>
                                </div>
                            </div>
                            <div className="desc-box">
                                <div className="top-bar"> Prompt </div>
                                <textarea
                                    className="content"
                                    onChange={e => this.setColDesc(field, e.target.value)}
                                    defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc}
                                    placeholder={this._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description/prompt to help with template generation.'}
                                />
                            </div> 
                        </>)
                    }
                    <div className="conditionals-section">
                        <span className="conditionals-title">Conditional Logic</span>
                        {conditionForm(field.title, undefined, true)}
                        {this.templateManager.conditionalFieldLogic[field.title]?.map(condition => conditionForm(condition.field, condition))}
                    </div>
                    </>
                }
            </div>
        );

        return (
            <div className="docCreatorMenu-dashboard-view">
                <div className="topbar">
                    <DocCreatorMenuButton icon={'plus'} function={this.addField}/>
                    <DocCreatorMenuButton icon={'arrow-left'} styles={'float-right'} function={() => runInAction(() => (this._menuContent = 'templates'))}/>
                </div>
                <div className="panels-container">{this.fieldsInfos.map((field, i) => fieldPanel(field, i))}</div>
            </div>
        );
    }

    @computed get editingView() { return <TemplateEditingWindow template={this._currEditingTemplate as Template} menu={this} /> }
    
    private optionsButtonOpts: [IconProp, () => any] = ['gear', () => (this._menuContent = 'dashboard')];

    get renderSelectedViewType() {
        switch (this._menuContent) {
            case 'templates': 
                return (
                    <div className='docCreatorMenu-templates-view'>
                        <div className="docCreatorMenu-templates-displays">
                            <TemplatePreviewGrid 
                                title={'Suggested Templates'} 
                                menu={this} 
                                loading={this._GPTLoading}
                                optionsButtonOpts={this.optionsButtonOpts}
                                templates={this._suggestedTemplates}
                            />
                            <div className="docCreatorMenu-GPT-options">
                                <div className="docCreatorMenu-GPT-options-container">
                                    <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generatePresetTemplates}/>
                                </div>
                            </div>
                        </div>
                    </div>
                )
            case 'templateEditing':
                return this.editingView
            case 'options':   return this.optionsMenuContents;
            case 'dashboard': return this.dashboardContents;
        } // prettier-ignore
        return undefined;
    }

    get resizePanes() {
        const ref = this._ref?.getBoundingClientRect();
        const height: number = ref?.height ?? 0;
        const width: number = ref?.width ?? 0;

        return [
            <div className='docCreatorMenu-resizer top' key='0' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: -7}}/>, 
            <div className='docCreatorMenu-resizer left' key='1' onPointerDown={this.onResizePointerDown} style={{height: height, left: -7, top: 0}}/>,
            <div className='docCreatorMenu-resizer right' key='2' onPointerDown={this.onResizePointerDown} style={{height: height, left: width - 3, top: 0}}/>, 
            <div className='docCreatorMenu-resizer bottom' key='3' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: height - 3}}/>,
            <div className='docCreatorMenu-resizer topLeft' key='4' onPointerDown={this.onResizePointerDown} style={{left: -10, top: -10, cursor: 'nwse-resize'}}/>,
            <div className='docCreatorMenu-resizer topRight' key='5' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: -10, cursor: 'nesw-resize'}}/>,
            <div className='docCreatorMenu-resizer bottomLeft' key='6' onPointerDown={this.onResizePointerDown} style={{left: -10, top: height - 5, cursor: 'nesw-resize'}}/>,
            <div className='docCreatorMenu-resizer bottomRight' key='7' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: height - 5, cursor: 'nwse-resize'}}/>,
        ]; //prettier-ignore
    }

    render() {
        const topButton = (icon: string, opt: string, func: () => void, tag: string) => (
            <div className={`top-button-container ${tag} ${opt === this._menuContent ? 'selected' : ''}`}>
                <div className="top-button-content" onPointerDown={e => this.setUpButtonClick(e, action(func))}>
                    <FontAwesomeIcon icon={icon as IconProp} />
                </div>
            </div>
        );

        const onPreviewSelected = () => (this._menuContent = 'templates');
        const onSavedSelected = () => (this._menuContent = 'dashboard');
        const onOptionsSelected = () => {
            this._menuContent = 'options';
            if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length));
        };

        return (
            <div className="docCreatorMenu">
                {!this._shouldDisplay ? undefined : (
                    <div
                        className="docCreatorMenu-cont"
                        ref={r => (this._ref = r)}
                        style={{
                            display: '',
                            left: this._pageX,
                            top: this._pageY,
                            width: this._menuDimensions.width,
                            height: this._menuDimensions.height,
                            background: SnappingManager.userBackgroundColor,
                            color: SnappingManager.userColor,
                        }}>
                        {this.resizePanes}
                        <div
                            className="docCreatorMenu-menu"
                            onPointerDown={e =>
                                setupMoveUpEvents(
                                    this,
                                    e,
                                    event => {
                                        this._dragging = true;
                                        this._startPos = { x: 0, y: 0 };
                                        this._startPos.x = event.pageX - (this._ref?.getBoundingClientRect().left ?? 0);
                                        this._startPos.y = event.pageY - (this._ref?.getBoundingClientRect().top ?? 0);
                                        document.addEventListener('pointermove', this.onDrag);
                                        return true;
                                    },
                                    emptyFunction,
                                    undoable(clickEv => clickEv.stopPropagation(), 'drag menu')
                                )
                            }>
                            <div className="docCreatorMenu-top-buttons-container">
                                {topButton('lightbulb', 'templates', onPreviewSelected, 'left')}
                                {topButton('magnifying-glass', 'options', onOptionsSelected, 'middle')}
                                {topButton('bars', 'saved', onSavedSelected, 'right')}
                            </div>
                            <DocCreatorMenuButton icon={'minus'} styles={'float-right'} function={this.closeMenu}/>
                        </div>
                        {this.renderSelectedViewType}
                    </div>
                )}
            </div>
        );
    }
}