/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox } from '@mui/material';
import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ClientUtils, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx, inheritParentAcls } from '../../../../fields/util';
import { DocUtils } from '../../../documents/DocUtils';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
import { UndoManager, undoable } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { MarqueeAnnotator } from '../../MarqueeAnnotator';
import { PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { FocusViewOptions } from '../FocusViewOptions';
import './DataVizBox.scss';
import { Histogram } from './components/Histogram';
import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
import { LinkManager } from '../../../util/LinkManager';
import { DataVizTemplateInfo, DataVizTemplateLayout, DocCreatorMenu, LayoutType } from './DocCreatorMenu';
import { CollectionFreeFormView, MarqueeView } from '../../collections/collectionFreeForm';
import { PrefetchProxy } from '../../../../fields/Proxy';
import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
import { template } from 'lodash';
import { data } from 'jquery';
import { listSpec } from '../../../../fields/Schema';
import { ObjectField } from '../../../../fields/ObjectField';
import { Id } from '../../../../fields/FieldSymbols';
import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';

export enum DataVizView {
    TABLE = 'table',
    LINECHART = 'lineChart',
    HISTOGRAM = 'histogram',
    PIECHART = 'pieChart',
}

@observer
export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
    private _marqueeref = React.createRef<MarqueeAnnotator>();
    private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
    private _disposers: { [name: string]: IReactionDisposer } = {};
    anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
    sidebarAddDoc: ((doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean) | undefined;
    crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
    @observable _marqueeing: number[] | undefined = undefined;
    @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
    @observable _specialHighlightedRow: number | undefined = undefined;

    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
        this._props.setContentViewBox?.(this);
    }

    @computed get annotationLayer() {
        TraceMobx();
        return <div className="dataVizBox-annotationLayer" style={{ height: this._props.PanelHeight(), width: this._props.PanelWidth() }} ref={this._annotationLayer} />;
    }
    marqueeDown = (e: React.PointerEvent) => {
        if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
            setupMoveUpEvents(
                this,
                e,
                action(moveEv => {
                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                    this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
                    return true;
                }),
                returnFalse,
                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
                false
            );
        }
    };
    @action
    finishMarquee = () => {
        this._marqueeref.current?.onTerminateSelection();
        this._props.select(false);
    };
    savedAnnotations = () => this._savedAnnotations;

    public static LayoutString(fieldStr: string) {
        return FieldView.LayoutString(DataVizBox, fieldStr);
    }

    // all datasets that have been retrieved from the server stored as a map from the dataset url to an array of records
    static dataset = new ObservableMap<string, { [key: string]: string }[]>();
    // when a dataset comes from schema view, this stores the original dataset to refer back to
    // href : dataset
    static datasetSchemaOG = new ObservableMap<string, { [key: string]: string }[]>();

    private _vizRenderer: LineChart | Histogram | PieChart | undefined;
    private _sidebarRef = React.createRef<SidebarAnnos>();

    // all CSV records in the dataset (that aren't an empty row)
    @computed.struct get records() {
        const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
        return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
    }

    // currently chosen visualization type: line, pie, histogram, table
    @computed get dataVizView(): DataVizView {
        return StrCast(this.layoutDoc._dataViz, 'table') as DataVizView;
    }

    @computed get dataUrl() {
        return Cast(this.dataDoc[this.fieldKey], CsvField);
    }
    @computed.struct get axes() {
        return StrListCast(this.layoutDoc._dataViz_axes);
    }
    selectAxes = (axes: string[]) => {
        this.layoutDoc._dataViz_axes = new List<string>(axes);
        //axes.forEach(axis => console.log(axis)) 
        //DocCreatorMenu.Instance.generateTemplates('');
    };
    @computed.struct get titleCol() {
        return StrCast(this.layoutDoc._dataViz_titleCol);
    }
    selectTitleCol = (titleCol: string) => {
        this.layoutDoc._dataViz_titleCol = titleCol;
    };

    @action setSpecialHighlightedRow = (row: number | undefined) => {
        this._specialHighlightedRow = row;
    }

    @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
    restoreView = (data: Doc) => {
        // const changedView = data.config_dataViz && this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
        // const changedAxes = data.config_dataVizAxes && this.axes.join('') !== StrListCast(data.config_dataVizAxes).join('') && (this.layoutDoc._dataViz_axes = new List<string>(StrListCast(data.config_dataVizAxes)));
        this.layoutDoc.dataViz_selectedRows = Field.Copy(data.dataViz_selectedRows);
        this.layoutDoc.dataViz_histogram_barColors = Field.Copy(data.dataViz_histogram_barColors);
        this.layoutDoc.dataViz_histogram_defaultColor = data.dataViz_histogram_defaultColor;
        this.layoutDoc.dataViz_pie_sliceColors = Field.Copy(data.dataViz_pie_sliceColors);
        Object.keys(this.layoutDoc).forEach(key => {
            if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
                this.layoutDoc['_' + key] = data[key];
            }
        });
        return true;
        // const func = () => this._vizRenderer?.restoreView(data);
        // if (changedView || changedAxes) {
        //     setTimeout(func, 100);
        //     return true;
        // }
        // return func() ?? false;
    };

    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
        const visibleAnchor = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation);
        const anchor = !pinProps
            ? this.Document
            : this._vizRenderer?.getAnchor(pinProps) ??
              visibleAnchor ??
              Docs.Create.ConfigDocument({
                  title: 'ImgAnchor:' + this.Document.title,
                  config_panX: NumCast(this.layoutDoc._freeform_panX),
                  config_panY: NumCast(this.layoutDoc._freeform_panY),
                  config_viewScale: Cast(this.layoutDoc._freeform_scale, 'number', null),
                  annotationOn: this.Document,
                  // when we clear selection -> we should have it so chartBox getAnchor returns undefined
                  // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
                  /* put in some options */
              });
        anchor.config_dataViz = this.dataVizView;
        anchor.config_dataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined;
        anchor.dataViz_selectedRows = Field.Copy(this.layoutDoc.dataViz_selectedRows);
        anchor.dataViz_histogram_barColors = Field.Copy(this.layoutDoc.dataViz_histogram_barColors);
        anchor.dataViz_histogram_defaultColor = this.layoutDoc.dataViz_histogram_defaultColor;
        anchor.dataViz_pie_sliceColors = Field.Copy(this.layoutDoc.dataViz_pie_sliceColors);
        Object.keys(this.layoutDoc).forEach(key => {
            if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
                anchor[key] = this.layoutDoc[key];
            }
        });

        this.addDocument(anchor);
        // addAsAnnotation && this.addDocument(anchor);
        return anchor;
    };

    createNoteAnnotation = () => {
        const createFunc = undoable(() => {
            this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
        }, 'create note annotation');
        if (!this.layoutDoc.layout_showSidebar) {
            this.toggleSidebar();
            setTimeout(createFunc);
        } else createFunc();
    };

    @observable _showSidebar = false;
    @observable _previewNativeWidth: Opt<number> = undefined;
    @observable _previewWidth: Opt<number> = undefined;
    @action
    toggleSidebar = () => {
        const prevWidth = this.sidebarWidth();
        this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
        this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
    };
    @computed get SidebarShown() {
        return !!this.layoutDoc._layout_showSidebar;
    }
    @computed get sidebarHandle() {
        return (
            <div
                className="dataviz-overlayButton-sidebar"
                key="sidebar"
                title="Toggle Sidebar"
                style={{
                    display: !this._props.isContentActive() ? 'none' : undefined,
                    top: StrCast(this.Document._layout_showTitle) === 'title' ? 20 : 5,
                    backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                }}
                onPointerDown={this.sidebarBtnDown}>
                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
            </div>
        );
    }
    /**
     * Toggle sidebar onclick the tiny comment button on the top right corner
     * @param e
     */
    sidebarBtnDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            (moveEv, down, delta) =>
                runInAction(() => {
                    const localDelta = this._props
                        .ScreenToLocalTransform()
                        .scale(this._props.NativeDimScaling?.() || 1)
                        .transformDirection(delta[0], delta[1]);
                    const fullWidth = NumCast(this.layoutDoc._width);
                    const mapWidth = fullWidth - this.sidebarWidth();
                    if (this.sidebarWidth() + localDelta[0] > 0) {
                        this.layoutDoc._layout_showSidebar = true;
                        this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
                        this.layoutDoc._width = fullWidth + localDelta[0];
                    } else {
                        this.layoutDoc._layout_showSidebar = false;
                        this.layoutDoc._width = mapWidth;
                        this.layoutDoc._layout_sidebarWidthPercent = '0%';
                    }
                    return false;
                }),
            emptyFunction,
            () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar')
        );
    };
    getView = async (doc: Doc, options: FocusViewOptions) => {
        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
            options.didMove = true;
            this.toggleSidebar();
        }
        return new Promise<Opt<DocumentView>>(res => {
            DocumentView.addViewRenderedCb(doc, dv => res(dv));
        });
    };
    @computed get sidebarWidthPercent() {
        return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
    }
    @computed get sidebarColor() {
        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
    }
    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
        if (!this.SidebarShown) this.toggleSidebar();
        return this.addDocument(doc, sidebarKey);
    };
    sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);

    componentDidMount() {
        this._props.setContentViewBox?.(this);
        if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
        this._disposers.datavis = reaction(
            () => {
                if (this.layoutDoc.dataViz_schemaLive === undefined) this.layoutDoc.dataViz_schemaLive = true;
                const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
                if (!getFrom?.schema_columnKeys) return undefined;
                const keys = StrListCast(getFrom?.schema_columnKeys).filter(key => key !== 'text');
                const children = DocListCast(getFrom?.[Doc.LayoutFieldKey(getFrom)]);
                const current: { [key: string]: string }[] = [];
                children
                    .filter(child => child)
                    .forEach(child => {
                        const row: { [key: string]: string } = {};
                        keys.forEach(key => {
                            let cell = child[key];
                            if (cell && (cell as string)) cell = cell.toString().replace(/,/g, '');
                            row[key] = StrCast(cell);
                        });
                        current.push(row);
                    });
                if (!this.layoutDoc._dataViz_schemaOG) {
                    // makes a copy of the original table for the "live" toggle
                    const csvRows = [];
                    csvRows.push(keys.join(','));
                    for (let i = 0; i < children.length - 1; i++) {
                        const eachRow = [];
                        for (let j = 0; j < keys.length; j++) {
                            let cell = children[i][keys[j]];
                            if (cell && (cell as string)) cell = cell.toString().replace(/,/g, '');
                            eachRow.push(cell);
                        }
                        csvRows.push(eachRow);
                    }
                    const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
                    const options = { x: 0, y: 0, title: 'schemaTable for static dataviz', _width: 300, _height: 100, type: 'text/csv' };
                    const file = new File([blob], 'schemaTable for static dataviz', options);
                    const loading = Docs.Create.LoadingDocument(file, options);
                    DocUtils.uploadFileToDoc(file, {}, loading);
                    this.layoutDoc._dataViz_schemaOG = loading;
                }
                const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc;
                const ogHref = CsvCast(ogDoc[this.fieldKey]) ? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
                const { href } = CsvCast(this.Document[this.fieldKey]).url;
                if (ogHref && !DataVizBox.datasetSchemaOG.has(href)) {
                    // sets original dataset to the var
                    const lastRow = current.pop();
                    DataVizBox.datasetSchemaOG.set(href, current);
                    current.push(lastRow!);
                    fetch('/csvData?uri=' + ogHref).then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.datasetSchemaOG.set(href, jsonRes))));
                }
                return current;
            },
            current => {
                if (current) {
                    const { href } = CsvCast(this.Document[this.fieldKey]).url;
                    if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current);
                    else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!);
                }
            },
            { fireImmediately: true }
        );
    }

    // recs = () => {
    //     let csvStr: string = '';
    //     this.records[rowId][col]
    // }

    fetchData = () => {
        if (!this.Document.dataViz_asSchema) {
            DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
            fetch('/csvData?uri=' + (this.dataUrl?.url.href ?? '')) //
                .then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, jsonRes))));
        }
    };

    // toggles for user to decide which chart type to view the data in
    @computed get renderVizView() {
        const scale = this._props.NativeDimScaling?.() || 1;
        const sharedProps = {
            Document: this.Document,
            layoutDoc: this.layoutDoc,
            records: this.records,
            axes: this.axes,
            titleCol: this.titleCol,
            // width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
            height: (this._props.PanelHeight() / scale - 55) /* height of 'change view' button */ * 0.8,
            width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
            margin: { top: 10, right: 25, bottom: 75, left: 45 },
        };
        if (!this.records.length) return 'no data/visualization';
        switch (this.dataVizView) {
            case DataVizView.TABLE:     return <TableBox  {...sharedProps} specHighlightedRow={this._specialHighlightedRow} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
            case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />;
            case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />;
            case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} 
            margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />;
            default:
        } // prettier-ignore
        return null;
    }

    @action
    onPointerDown = (e: React.PointerEvent): void => {
        if ((this.Document._freeform_scale || 1) !== 1) return;
        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
            this._props.select(false);
            MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
            this._marqueeing = [e.clientX, e.clientY];
            const target = e.target as any;
            if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
                /* empty */
            } else {
                // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
                setTimeout(
                    action(() => {
                        this._marqueeing = undefined;
                    }),
                    100
                ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.

                document.addEventListener('pointerup', this.onSelectEnd);
            }
        }
    };

    @action
    onSelectEnd = (e: PointerEvent): void => {
        this._props.select(false);
        document.removeEventListener('pointerup', this.onSelectEnd);

        const sel = window.getSelection();
        if (sel) {
            AnchorMenu.Instance.setSelectedText(sel.toString());
        }

        if (sel?.type === 'Range') {
            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
        }

        // Changing which document to add the annotation to (the currently selected PDF)
        GPTPopup.Instance.setSidebarId('data_sidebar');
        GPTPopup.Instance.addDoc = this.sidebarAddDocument;
    };

    // represents whether or not a data viz box created from a schema table displays live updates to the canvas
    @action
    changeLiveSchemaCheckbox = () => {
        this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive;
    };

    // represents whether or not clicking on a peice of data in the visualization
    //      (i.e. a data point in a linechart, a bar on a histogram, or a slice of a pie chart)
    //      filters the data onto a new data viz doc created off of this one
    @action
    changeFilteringCheckbox = () => {
        this.layoutDoc.dataViz_filterSelection = !this.layoutDoc.dataViz_filterSelection;
    };

    openDocCreatorMenu = (x: number, y: number) => {
        DocCreatorMenu.Instance.toggleDisplay(x, y);
        DocCreatorMenu.Instance.setDataViz(this);
        DocCreatorMenu.Instance.setTemplateDocs(this.getPossibleTemplates());
    }

    specificContextMenu = (x: number, y: number): void => {
        const cm = ContextMenu.Instance;
        const options = cm.findByDescription('Options...');
        const optionItems = options && 'subitems' in options ? options.subitems : [];
        optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
        optionItems.push({ description: `Create documents`, event: () => this.openDocCreatorMenu(x, y), icon: 'table-cells' });
        !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
    };

    askGPT = action(async () => {
        GPTPopup.Instance.setSidebarId('data_sidebar');
        GPTPopup.Instance.addDoc = this.sidebarAddDocument;
        GPTPopup.Instance.createFilteredDoc = this.createFilteredDoc;
        GPTPopup.Instance.setDataJson('');
        GPTPopup.Instance.setMode(GPTPopupMode.DATA);
        const data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
        GPTPopup.Instance.setDataJson(JSON.stringify(data));
        GPTPopup.Instance.generateDataAnalysis();
    });

    getPossibleTemplates = (): Doc[] => {
        const linkedDocs: Doc[] = LinkManager.Instance.getAllRelatedLinks(this.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)));
        const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'config').map(doc => DocCast(doc.annotationOn));
        const isColumnTitle = (title: string): boolean => {
            const colTitles: string[] = Object.keys(this.records[0]);
            for (let i = 0; i < colTitles.length; ++i){
                if (colTitles[i] === title) {
                    console.log(true); 
                    return true;
                }
            }
            return false;
        }
        const isValidTemplate = (collection: Doc) => {
            const childDocs = DocListCast(collection[Doc.LayoutFieldKey(collection)]);
            for (let i = 0; i < childDocs.length; ++i){
                if (isColumnTitle(String(childDocs[i].title))) return true;
            }
            return false;
        }
        return linkedCollections.filter(col => isValidTemplate(col));
    }

    ApplyTemplateTo = (templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) => {
        if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) {
            if (target.resolvedDataDoc) {
                target[targetKey] = new PrefetchProxy(templateDoc);
            } else {
                titleTarget && (Doc.GetProto(target).title = titleTarget);
                const setDoc = [AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
                setDoc[targetKey] = new PrefetchProxy(templateDoc);
            }
        }
        return target;
    }

    applyLayout = (templateInfo: DataVizTemplateInfo, docs: Doc[]) => {
        if (templateInfo.layout.type === LayoutType.Stacked) return;
        const columns: number = templateInfo.columns;
        const xGap: number = templateInfo.layout.xMargin;
        const yGap: number = templateInfo.layout.yMargin;
        const repeat: number = templateInfo.layout.repeat;
        const startX: number = templateInfo.referencePos.x;
        const startY: number = templateInfo.referencePos.y;
        const templWidth = Number(templateInfo.doc._width);
        const templHeight = Number(templateInfo.doc._height);

        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 += templWidth + xGap;
                ++docsChanged;
                ++i;
            }

            i = 0;
            curX = startX;
            curY += templHeight + yGap;
        }
    }

    // @action addSavedLayout = (layout: DataVizTemplateLayout) => {
    //     const saved = Cast(this.layoutDoc.dataViz_savedTemplates, listSpec('RefField'));
        
    // }

    @action
    createDocsFromTemplate = (templateInfo: DataVizTemplateInfo) => {
        if (!templateInfo.doc) return;
        const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
        const fields: string[] = Array.from(Object.keys(this.records[0])); 
        const selectedRows = NumListCast(this.layoutDoc.dataViz_selectedRows);
        const docs: Doc[] = selectedRows.map(row => {
            const values: String[] = [];
            fields.forEach(col => values.push(this.records[row][col]));

            const proto = new Doc();
            proto.author = ClientUtils.CurrentUserEmail();
            values.forEach((val, i) => {proto[fields[i]] = val as FieldType});
            
            const target = Doc.MakeDelegate(proto);
            const targetKey = StrCast(templateInfo.doc!.layout_fieldKey, 'layout');
            const applied = this.ApplyTemplateTo(templateInfo.doc!, target, targetKey, templateInfo.doc!.title + `${row}`);
            target.layout_fieldKey = targetKey;
            //this.applyImagesTo(target, fields);
            return applied;
        });

        docs.forEach(doc => mainCollection.addDocument(doc));

        this.applyLayout(templateInfo, docs);
    }

    /**
     * creates a new dataviz document filter from this one
     *      it appears to the right of this document, with the
     *      parameters passed in being used to create an initial display
     */
    createFilteredDoc = (axes?: any) => {
        const embedding = Doc.MakeEmbedding(this.Document!);
        embedding._layout_showSidebar = false;
        embedding._dataViz = DataVizView.LINECHART;
        embedding._dataViz_axes = new List<string>(axes);
        embedding._dataViz_parentViz = this.Document;
        embedding.histogramBarColors = Field.Copy(this.layoutDoc.histogramBarColors);
        embedding.defaultHistogramColor = this.layoutDoc.defaultHistogramColor;
        embedding.pieSliceColors = Field.Copy(this.layoutDoc.pieSliceColors);
        embedding._layout_showSidebar = false;
        embedding.width = NumCast(this.layoutDoc._width) - this.sidebarWidth();
        embedding._layout_sidebarWidthPercent = '0%';
        this._props.addDocument?.(embedding);
        embedding._dataViz_axes = new List<string>(axes);
        this.layoutDoc.dataViz_selectedRows = new List<number>(this.records.map((rec, i) => i));
        embedding.x = Number(embedding.x) + Number(this.Document.width);

        return true;
    };

    render() {
        const scale = this._props.NativeDimScaling?.() || 1;
        const toggleBtn = (name: string, type: DataVizView) => (
            <Toggle
                text={name}
                toggleType={ToggleType.BUTTON}
                type={Type.SEC}
                color="black"
                onClick={() => {
                    this.layoutDoc._dataViz = type;
                }}
                toggleStatus={this.layoutDoc._dataViz === type}
            />
        );
        return !this.records.length ? (
            // displays how to get data into the DataVizBox if its empty
            <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command (ctrl + p) to bring the data table to your canvas.</div>
        ) : (
            <div
                className="dataViz-box"
                onPointerDown={this.marqueeDown}
                style={{
                    pointerEvents: this._props.isContentActive() === true ? 'all' : 'none',
                    width: `${100 / scale}%`,
                    height: `${100 / scale}%`,
                    transform: `scale(${scale})`,
                    position: 'absolute',
                }}
                onContextMenu={(e) => this.specificContextMenu(e.pageX, e.pageY)}
                onWheel={e => e.stopPropagation()}
                ref={this._mainCont}>
                <div className="datatype-button">
                    {toggleBtn('  TABLE  ', DataVizView.TABLE)}
                    {toggleBtn('LINECHART', DataVizView.LINECHART)}
                    {toggleBtn('HISTOGRAM', DataVizView.HISTOGRAM)}
                    {toggleBtn('PIE CHART', DataVizView.PIECHART)}
                </div>

                {this.layoutDoc && this.layoutDoc.dataViz_asSchema ? (
                    <div className="displaySchemaLive">
                        <div className="liveSchema-checkBox" style={{ width: this._props.width }}>
                            <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
                            Display Live Updates to Canvas
                        </div>
                    </div>
                ) : null}
                {this.layoutDoc._dataViz !== DataVizView.TABLE ? (
                    <div className="filterData-checkBox">
                        <Checkbox color="primary" onChange={this.changeFilteringCheckbox} checked={this.layoutDoc.dataViz_filterSelection as boolean} />
                        Select data to filter
                    </div>
                ) : null}

                {this.renderVizView}

                <div className="dataviz-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }} onPointerDown={this.onPointerDown}>
                    <SidebarAnnos
                        ref={this._sidebarRef}
                        {...this._props}
                        fieldKey={this.fieldKey}
                        Document={this.Document}
                        layoutDoc={this.layoutDoc}
                        dataDoc={this.dataDoc}
                        usePanelWidth
                        showSidebar={this.SidebarShown}
                        nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                        PanelWidth={this.sidebarWidth}
                        sidebarAddDocument={this.sidebarAddDocument}
                        moveDocument={this.moveDocument}
                        removeDocument={this.sidebarRemoveDocument}
                    />
                </div>
                {this.sidebarHandle}
                {this.annotationLayer}
                {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
                    <MarqueeAnnotator
                        ref={this._marqueeref}
                        Document={this.Document}
                        anchorMenuClick={this.anchorMenuClick}
                        scrollTop={0}
                        annotationLayerScrollTop={NumCast(this.Document._layout_scrollTop)}
                        scaling={returnOne}
                        docView={this.DocumentView}
                        addDocument={this.sidebarAddDocument}
                        finishMarquee={this.finishMarquee}
                        savedAnnotations={this.savedAnnotations}
                        selectionText={returnEmptyString}
                        annotationLayer={this._annotationLayer.current}
                        marqueeContainer={this._mainCont.current}
                        anchorMenuCrop={this.crop}
                    />
                )}
            </div>
        );
    }
}
Docs.Prototypes.TemplateMap.set(DocumentType.DATAVIZ, {
    layout: { view: DataVizBox, dataField: 'data' },
    options: {
        acl: '',
        dataViz_title: '',
        dataViz_line: '',
        dataViz_pie: '',
        dataViz_histogram: '',
        dataViz: 'table',
        _layout_fitWidth: true,
        _layout_reflowHorizontal: true,
        _layout_reflowVertical: true,
        _layout_nativeDimEditable: true,
    },
});

Docs.Prototypes.TemplateMap.set(DocumentType.DATAVIZ, {
    layout: { view: DataVizBox, dataField: 'data' },
    options: { dataViz_title: '', dataViz_line: '', dataViz_pie: '', dataViz_histogram: '', dataViz: 'table', _layout_fitWidth: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true },
});
