import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Colors } from 'browndash-components';
import { action, computed, makeObservable, observable, reaction, 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, returnTrue, setupMoveUpEvents } from '../../../../../ClientUtils';
import { emptyFunction } from '../../../../../Utils';
import { Doc, FieldType, NumListCast, StrListCast, returnEmptyDoclist } from '../../../../../fields/Doc';
import { Id } from '../../../../../fields/FieldSymbols';
import { Cast, DocCast, ImageCast, StrCast } from '../../../../../fields/Types';
import { ImageField } from '../../../../../fields/URLField';
import { Networking } from '../../../../Network';
import { GPTCallType, gptAPICall, gptImageCall } from '../../../../apis/gpt/GPT';
import { Docs, DocumentOptions } from '../../../../documents/Documents';
import { DragManager } from '../../../../util/DragManager';
import { MakeTemplate } from '../../../../util/DropConverter';
import { SnappingManager } from '../../../../util/SnappingManager';
import { UndoManager, undoable } from '../../../../util/UndoManager';
import { LightboxView } from '../../../LightboxView';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
import { CollectionFreeFormView } from '../../../collections/collectionFreeForm/CollectionFreeFormView';
import { DocumentView, DocumentViewInternal } from '../../DocumentView';
import { FieldViewProps } from '../../FieldView';
import { OpenWhere } from '../../OpenWhere';
import { DataVizBox } from '../DataVizBox';
import './DocCreatorMenu.scss';
import { DefaultStyleProvider, returnEmptyDocViewList } from '../../../StyleProvider';
import { Transform } from '../../../../util/Transform';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend';
import { TemplateManager } from './TemplateManager';
import { Template } from './Template';
import { Field, ViewType } from './FieldTypes/Field';
import { TabDocView } from '../../../collections/TabDocView';

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

@observer
export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
    static Instance: DocCreatorMenu;

    private DEBUG_MODE: boolean = false;

    private _disposers: { [name: string]: IDisposer } = {};

    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 _userCreatedFields: Col[] = [];
    @observable _selectedCols: { title: string; type: string; desc: string }[] | undefined = [];

    @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 _expandedPreview: Doc | undefined = undefined;

    @observable _suggestedTemplates: Template[] = [];
    @observable _GPTOpt: boolean = false;
    @observable _callCount: number = 0;
    @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' = 'templates';
    @observable _dragging: boolean = false;
    @observable _draggingIndicator: boolean = false;
    @observable _dataViz?: DataVizBox;
    @observable _interactionLock: any;
    @observable _snapPt: any;
    @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 _editing: boolean = false;

    constructor(props: any) {
        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 addUserTemplate = (template: Template) => {
        this._userTemplates.push(template);
    };
    @action removeUserTemplate = (template: Template) => {
        this._userTemplates.splice(this._userTemplates.indexOf(template), 1);
    }
    @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: any, func: Function) => {
        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);
        //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')})
        this._disposers.lightbox = reaction(
            () => LightboxView.LightboxDoc(),
            doc => {
                doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu();
            }
        );
        // this._disposers.layout = reaction(
        //     () => this._layout,
        //     layout => { this.updateRenderedDocCollection(); }
        // );
        //this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', desc: ''}}))
    }

    componentWillUnmount() {
        Object.values(this._disposers).forEach(disposer => disposer?.());
        document.removeEventListener('pointerdown', this.onPointerDown, true);
        document.removeEventListener('pointerup', this.onPointerUp);
    }

    @action
    updateSelectedCols = (cols: string[]) => {
        this._selectedCols;
    };

    @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 any).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: any): 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<any>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
        }); // prettier-ignore
        return true;
    };

    @action
    onDrag = (e: any): 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 }) => {
        const refCent = [refPt[0], refPt[1]]; // fixed reference point for resize (ie, a point that doesn't move)
        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;
    };

    async getIcon(doc: Doc) {
        const docView = DocumentView.getDocumentView(doc);
        if (docView) {
            docView.ComponentView?.updateIcon?.();
            return new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 500));
        }
        return undefined;
    }

    @action updateRenderedPreviewCollection = async (template: Template) => {
        this._fullyRenderedDocs = await this.createDocsFromTemplate(template) ?? [];
        console.log(this._fullyRenderedDocs);
        this.updateRenderedDocCollection();
    }

    @action updateSelectedTemplate = async (template: Template) => {
        if (this._selectedTemplate === template) {
            this._selectedTemplate = undefined;
            return;
        } else {
            this._selectedTemplate = template;
            //template.renderUpdates();
            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) => {
        //this.closeMenu();
        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: [] }]);
        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 (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();
    };

    generateGPTImage = async (prompt: string): Promise<string | undefined> => {
        console.log(prompt);

        try {
            const res = await gptImageCall(prompt);

            if (res) {
                const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res });
                const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client);
                return source;
            }
        } catch (e) {
            console.log(e);
        }
    };

    /**
     * Populates a preset template framework with content from a datavizbox or any AI-generated content.
     * @param template the preloaded template framework being filled in
     * @param assignments a list of template field numbers (from top to bottom) and their assigned columns from the linked dataviz
     * @returns a doc containing the fully rendered template
     */
    applyGPTContentToTemplate = async (template: Template, assignments: { [field: string]: Col }): Promise<Template | undefined> => {

        const GPTTextCalls = Object.entries(assignments).filter(([str, col]) => col.type === TemplateFieldType.TEXT && this._userCreatedFields.includes(col));
        const GPTIMGCalls = Object.entries(assignments).filter(([str, col]) => col.type === TemplateFieldType.VISUAL && this._userCreatedFields.includes(col));

        let fieldContent: string = template.compiledContent;

        if (GPTTextCalls.length) {
            const promises = GPTTextCalls.map(([str, col]) => {
                return this.renderGPTTextCall(template, col, Number(str));
            });

            await Promise.all(promises);
        }

        console.log(GPTIMGCalls)

        if (GPTIMGCalls.length) {
            const promises = GPTIMGCalls.map(async ([fieldNum, col]) => {
                return this.renderGPTImageCall(template, col, Number(fieldNum));
            });

            await Promise.all(promises);
        };

        return template;
    };

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

        ++this._callCount;
        const origCount = this._callCount;

        let prompt: string = `(${origCount}) ${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(template => template.title === tempTitle)[0];
                    if (!template) return;
                    const toObj = Object.entries(assignment).reduce(
                        (a, [fieldID, colTitle]) => {
                            const col = this.getColByTitle(colTitle);
                            if (!this._userCreatedFields.includes(col)){ // do the following for any fields not added by the user; will change in the future, for now only GPT content works with user-added field
                                const field = template.getFieldByID(Number(fieldID));
                                field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT);
                                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 cols = this.fieldsInfos;
            templates.push(...this.templateManager.getValidTemplates(cols));

            console.log(templates)

            const assignments: [Template, { [field: number]: Col }][] = await this.assignColsToFields(templates, cols);

            const renderedTemplatePromises: Promise<Template | undefined>[] = assignments.map(([template, assignments]) => this.applyGPTContentToTemplate(template, assignments));

            const renderedTemplates: (Template | undefined)[] = await Promise.all(renderedTemplatePromises);
        }

        // templates.forEach(template => template.mainField.initRenderedDoc())

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

    renderGPTImageCall = async (template: Template, col: Col, fieldNum: number): Promise<boolean> => {
        const generateAndLoadImage = async (fieldNum: string, col: Col, prompt: string) => {
            const url = await this.generateGPTImage(prompt);
            console.log('url: ', url)
            const field: Field = template.getFieldByID(Number(fieldNum));

            field.setContent(url ?? '', ViewType.IMG);
            field.setTitle(col.title);
        };

        let fieldContent: string = template.compiledContent;

        try {
            const sysPrompt =
                'Your job is to create a prompt for an AI image generator to help it generate an image based on existing content in a template and a user prompt. Your prompt should focus heavily on visual elements to help the image generator; avoid unecessary info that might distract it. ONLY INCLUDE THE PROMPT, NO OTHER TEXT OR EXPLANATION. The existing content is as follows: ' +
                fieldContent +
                ' **** The user prompt is: ' +
                col.desc;

            const prompt = await gptAPICall(sysPrompt, GPTCallType.COMPLETEPROMPT);

            await generateAndLoadImage(String(fieldNum), col, prompt);
        } catch (e) {
            console.log(e);
        }
        return true;
    }

    renderGPTTextCall = async (template: Template, col: Col, fieldNum: number): Promise<boolean> => {
        const wordLimit = (size: TemplateFieldSize) => {
            switch (size) {
                case TemplateFieldSize.TINY:
                    return 2;
                case TemplateFieldSize.SMALL:
                    return 5;
                case TemplateFieldSize.MEDIUM:
                    return 20;
                case TemplateFieldSize.LARGE:
                    return 50;
                case TemplateFieldSize.HUGE:
                    return 100;
                default:
                    return 10;
            }
        };

        const textAssignment = `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`;

        let fieldContent: string = template.compiledContent;

        try {
            const prompt = fieldContent + textAssignment;

            const res = await gptAPICall(`${++this._callCount}: ${prompt}`, GPTCallType.FILL);

            console.log('prompt: ', prompt, ' response: ', res);

            if (res) {
                const assignments: { [title: string]: { number: string; content: string } } = JSON.parse(res);
                Object.entries(assignments).forEach(([title, info]) => {
                    const field: Field = template.getFieldByID(Number(info.number));
                    const col = this.getColByTitle(title);

                    field.setContent(info.content ?? '', ViewType.TEXT);
                    field.setTitle(col.title);
                });
            }
        } catch (err) {
            console.log(err);
        }

        return true;
    }

    createDocsFromTemplate = async (template: Template) => {
        const dv = this._dataViz;

        if (!dv) return;

        this._docsRendering = true;

        const fields: string[] = Array.from(Object.keys(dv.records[0]));  
        const selectedRows = NumListCast(dv.layoutDoc.dataViz_selectedRows);
    
        const rowContents: { [title: string]: string }[] = selectedRows.map(row => {
            let values: { [title: string]: string } = {};
            fields.forEach(col => {
                values[col] = dv.records[row][col];
            });
    
            return values;
        });
    
        const processContent = async (content: {[title: string]: string}) => {

            const templateCopy = template.cloneBase(); 

            fields.filter(title => title).forEach(title => {
                const field = templateCopy.getFieldByTitle(title);
                if (field === undefined) return;
                field.setContent(content[title], field.viewType);
            });
    
            const gptPromises = this._userCreatedFields.filter(field => field.type === TemplateFieldType.TEXT).map(field => {
                const title = field.title;
                const templateField = templateCopy.getFieldByTitle(title);
                if (templateField === undefined) return;
                const templatefieldID = templateField.getID;
    
                return this.renderGPTTextCall(templateCopy, field, templatefieldID);
            });

            const imagePromises = this._userCreatedFields.filter(field => field.type === TemplateFieldType.VISUAL).map(field => {
                const title = field.title;
                const templateField = templateCopy.getFieldByTitle(title);
                if (templateField === undefined) return;
                const templatefieldID = templateField.getID;
    
                return this.renderGPTImageCall(templateCopy, field, templatefieldID);
            });
    
            await Promise.all(gptPromises);

            await Promise.all(imagePromises);
    
            return templateCopy.getRenderedDoc();
        };

        const rows = [1, 2, 3, 4];
    
        //const promises = rowContents.map(content => processContent(content));
    
        const promises = rows.map(row => processContent({}));

        const renderedDocs = await Promise.all(promises);

        this._docsRendering = false; // removes loading indicator

        console.log('docs: ', renderedDocs);
        console.log('first doc height: ', renderedDocs[0]._height);

        return renderedDocs;
    }

    addRenderedCollectionToMainview = () => {
        const collection = this._renderedDocCollection;
        if (!collection) return;
        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 setExpandedView = (template: Template | undefined) => {
        if (template) {
            this._currEditingTemplate = template;
            this._expandedPreview = template.doc; //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''});
        } else {
            this._currEditingTemplate = undefined;
            this._expandedPreview = undefined;
        }
    };

    get editingWindow(){
        const rendered = !this._expandedPreview ? null :
            <div className="docCreatorMenu-expanded-template-preview">
                <DocumentView
                    Document={this._expandedPreview}
                    isContentActive={emptyFunction}
                    addDocument={returnFalse}
                    moveDocument={returnFalse}
                    removeDocument={returnFalse}
                    PanelWidth={() => this._menuDimensions.width - 10}
                    PanelHeight={() => this._menuDimensions.height - 60}
                    ScreenToLocalTransform={() => new Transform(-this._pageX - 5,-this._pageY - 35, 1)}
                    renderDepth={5}
                    whenChildContentsActiveChanged={emptyFunction}
                    focus={emptyFunction}
                    styleProvider={DefaultStyleProvider}
                    addDocTab={DocumentViewInternal.addDocTabFunc}
                    // eslint-disable-next-line no-use-before-define
                    pinToPres={() => undefined}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                    fitContentsToBox={returnFalse}
                    fitWidth={returnFalse}
                />
            </div>

        return (
            <div className="docCreatorMenu-expanded-template-preview">
                <div className="top-panel"/>
                {rendered}
                <div className="right-buttons-panel">
                    <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this.setUpButtonClick(e, () => {
                        if (!this._currEditingTemplate) return; 
                        if (this._currEditingTemplate === this._selectedTemplate) {
                            this.updateRenderedPreviewCollection(this._currEditingTemplate);
                        }
                        this.setExpandedView(undefined);
                        })}>
                        <FontAwesomeIcon icon="minimize" />
                    </button>
                    <button className="docCreatorMenu-menu-button section-reveal-options top-right-lower" onPointerDown={e => this.setUpButtonClick(e, () => {this._currEditingTemplate?.printFieldInfo();/*this._currEditingTemplate?.resetToBase();*/ this.setExpandedView(this._currEditingTemplate);})}>
                        <FontAwesomeIcon icon="arrows-rotate" color="white" />
                    </button>
                </div>
            </div>
        );
    }

    get templatesPreviewContents() {

        const GPTOptions = <div></div>;

        const previewDoc = (doc: Doc, template: Template) => 
        <DocumentView
        Document={doc}
        isContentActive={emptyFunction} // !!! should be return false
        addDocument={returnFalse}
        moveDocument={returnFalse}
        removeDocument={returnFalse}
        PanelWidth={() => this._selectedTemplate === template ? 104 : 111}
        PanelHeight={() => this._selectedTemplate === template ? 104 : 111}
        ScreenToLocalTransform={() => new Transform(-this._pageX - 5,-this._pageY - 35, 1)}
        renderDepth={1}
        whenChildContentsActiveChanged={emptyFunction}
        focus={emptyFunction}
        styleProvider={DefaultStyleProvider}
        addDocTab={this._props.addDocTab}
        // eslint-disable-next-line no-use-before-define
        pinToPres={() => undefined}
        childFilters={returnEmptyFilter}
        childFiltersByRanges={returnEmptyFilter}
        searchFilterDocs={returnEmptyDoclist}
        fitContentsToBox={returnFalse}
        fitWidth={returnFalse}
        hideDecorations={true}
        />

        //<img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} />

        return (
            <div className={`docCreatorMenu-templates-view`}>
                {this._expandedPreview ? (
                    this.editingWindow
                ) : (
                    <div>
                        <div className="docCreatorMenu-section" style={{ height: this._GPTOpt ? 200 : 200 }}>
                            <div className="docCreatorMenu-section-topbar">
                                <div className="docCreatorMenu-section-title">Suggested Templates</div>
                                <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => (this._menuContent = 'dashboard')))}>
                                    <FontAwesomeIcon icon="gear" />
                                </button>
                            </div>
                            <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this._GPTLoading || this._menuDimensions.width > 400 ? 'center' : '' }}>
                                {this._GPTLoading ? (
                                    <div className="loading-spinner">
                                        <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
                                    </div>
                                ) : (
                                    this._suggestedTemplates
                                        .map(template => (
                                            <div
                                                className="docCreatorMenu-preview-window"
                                                style={{
                                                    border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
                                                    boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
                                                }}
                                                onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(template)))}>
                                                <button
                                                    className="option-button left"
                                                    onPointerDown={e =>
                                                        this.setUpButtonClick(e, () => {
                                                            this.setExpandedView(template);
                                                        })
                                                    }>
                                                    <FontAwesomeIcon icon="magnifying-glass" color="white" />
                                                </button>
                                                <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.addUserTemplate(template))}>
                                                    <FontAwesomeIcon icon="plus" color="white" />
                                                </button>
                                                {previewDoc(template.getRenderedDoc(), template)}
                                            </div>
                                        ))
                                )}
                            </div>
                            <div className="docCreatorMenu-GPT-options">
                                <div className="docCreatorMenu-GPT-options-container">
                                    <button className="docCreatorMenu-menu-button" onPointerDown={e => this.setUpButtonClick(e, () => this.generatePresetTemplates())}>
                                        <FontAwesomeIcon icon="arrows-rotate" />
                                    </button>
                                </div>
                                {this._GPTOpt ? GPTOptions : null}
                            </div>
                        </div>
                        <hr className="docCreatorMenu-option-divider full no-margin" />
                        <div className="docCreatorMenu-section">
                            <div className="docCreatorMenu-section-topbar">
                                <div className="docCreatorMenu-section-title">Your Templates</div>
                                <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, () => (this._GPTOpt = !this._GPTOpt))}>
                                    <FontAwesomeIcon icon="gear" />
                                </button>
                            </div>
                            <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this._menuDimensions.width > 400 ? 'center' : '' }}>
                                <div className="docCreatorMenu-preview-window empty" onPointerDown={e => this.testTemplate()}>
                                    <FontAwesomeIcon icon="plus" color="rgb(160, 160, 160)" />
                                </div>
                                {this._userTemplates
                                    .map(template => (
                                        <div
                                            className="docCreatorMenu-preview-window"
                                            style={{
                                                border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
                                                boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
                                            }}
                                            onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(template)))}>
                                            <button
                                                className="option-button left"
                                                onPointerDown={e =>
                                                    this.setUpButtonClick(e, () => {
                                                        this.setExpandedView(template);
                                                    })
                                                }>
                                                <FontAwesomeIcon icon="magnifying-glass" color="white" />
                                            </button>
                                            <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.removeUserTemplate(template))}>
                                                <FontAwesomeIcon icon="minus" color="white" />
                                            </button>
                                            {previewDoc(template.getRenderedDoc(), template)}
                                        </div>
                                    ))}
                            </div>
                        </div>
                    </div>
                )}
            </div>
        );
    }


    @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: Function, 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 any} />
                    </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;
        }
    }

    screenToLocalTransform = () => this._props.ScreenToLocalTransform();

    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 = () => {
        console.log('called');
        if (!this._fullyRenderedDocs) return;

        //const largerSpan: number = horizontalSpan > verticalSpan ? horizontalSpan : verticalSpan;

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

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

        this.applyLayout(collection, this._fullyRenderedDocs);

        this._renderedDocCollection = collection;

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

        console.log('changed to: ', collection);
    }

    layoutPreviewContents = (id?: number) => {

        return this._docsRendering ? (
            <div className="docCreatorMenu-layout-preview-window-wrapper loading" id={String(id) ?? undefined}>
                <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" id={String(id) ?? undefined}>
                <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}
                    // eslint-disable-next-line no-use-before-define
                    pinToPres={() => undefined}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                    fitContentsToBox={returnFalse}
                    fitWidth={returnFalse}
                    hideDecorations={true}
                />
            </div>
        )
    };

    get optionsMenuContents() {
        const layoutEquals = (layout: DataVizTemplateLayout) => {}; //TODO: ADD LATER

        const layoutOption = (option: LayoutType, optStyle?: {}, specialFunc?: Function) => {
            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 any} />
                    </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(this._menuDimensions.width * 0.75)}
                {selectionBox(
                    60,
                    20,
                    'repeat',
                    undefined,
                    repeatOptions.map(num => <option onPointerDown={e => (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) 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>
        );
    }

    get dashboardContents() {
        const sizes: string[] = ['tiny', 'small', 'medium', 'large', 'huge'];

        const fieldPanel = (field: Col, id: number) => {
            return (
                <div className="field-panel" key={id}>
                    <div className="top-bar">
                        <span className="field-title">{`${field.title} Field`}</span>
                        <button className="docCreatorMenu-menu-button section-reveal-options no-margin" onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))} style={{ position: 'absolute', right: '0px' }}>
                            <FontAwesomeIcon icon="minus" />
                        </button>
                    </div>
                    <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' : ''}</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>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div className="sizes-box">
                        <div className="top-bar"> Valid Sizes </div>
                        <div className="content">
                            <div className="bubbles">
                                {sizes.map(size => (
                                    <>
                                        <input
                                            className="bubble"
                                            type="checkbox"
                                            name="type"
                                            checked={field.sizes.includes(size as TemplateFieldSize)}
                                            onChange={e => {
                                                this.modifyColSizes(field, size as TemplateFieldSize, e.target.checked);
                                            }}
                                        />
                                        <div className="text">{size}</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>
            );
        };

        return (
            <div className="docCreatorMenu-dashboard-view">
                <div className="topbar">
                    <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, this.addField)}>
                        <FontAwesomeIcon icon="plus" />
                    </button>
                    <button className="docCreatorMenu-menu-button section-reveal-options float-right" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => (this._menuContent = 'templates')))}>
                        <FontAwesomeIcon icon="arrow-left" />
                    </button>
                </div>
                <div className="panels-container">{this.fieldsInfos.map((field, i) => fieldPanel(field, i))}</div>
            </div>
        );
    }

    get renderSelectedViewType() {
        switch (this._menuContent) {
            case 'templates':
                return this.templatesPreviewContents;
            case 'options':
                return this.optionsMenuContents;
            case 'dashboard':
                return this.dashboardContents;
            default:
                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' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: -7}}/>, 
            <div className='docCreatorMenu-resizer left' onPointerDown={this.onResizePointerDown} style={{height: height, left: -7, top: 0}}/>,
            <div className='docCreatorMenu-resizer right' onPointerDown={this.onResizePointerDown} style={{height: height, left: width - 3, top: 0}}/>, 
            <div className='docCreatorMenu-resizer bottom' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: height - 3}}/>,
            <div className='docCreatorMenu-resizer topLeft' onPointerDown={this.onResizePointerDown} style={{left: -10, top: -10, cursor: 'nwse-resize'}}/>,
            <div className='docCreatorMenu-resizer topRight' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: -10, cursor: 'nesw-resize'}}/>,
            <div className='docCreatorMenu-resizer bottomLeft' onPointerDown={this.onResizePointerDown} style={{left: -10, top: height - 5, cursor: 'nesw-resize'}}/>,
            <div className='docCreatorMenu-resizer bottomRight' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: height - 5, cursor: 'nwse-resize'}}/>,
        ]; //prettier-ignore
    }

    render() {
        const topButton = (icon: string, opt: string, func: Function, tag: string) => {
            return (
                <div className={`top-button-container ${tag} ${opt === this._menuContent ? 'selected' : ''}`}>
                    <div
                        className="top-button-content"
                        onPointerDown={e =>
                            this.setUpButtonClick(e, () =>
                                runInAction(() => {
                                    func();
                                })
                            )
                        }>
                        <FontAwesomeIcon icon={icon as any} />
                    </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,
                                    e => {
                                        this._dragging = true;
                                        this._startPos = { x: 0, y: 0 };
                                        this._startPos.x = e.pageX - (this._ref?.getBoundingClientRect().left ?? 0);
                                        this._startPos.y = e.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>
                            <button className="docCreatorMenu-menu-button close-menu" onPointerDown={e => this.setUpButtonClick(e, this.closeMenu)}>
                                <FontAwesomeIcon icon={'minus'} />
                            </button>
                        </div>
                        {this.renderSelectedViewType}
                    </div>
                )}
            </div>
        );
    }
}

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