import React = require("react");
import { CursorProperty } from "csstype";
import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { Copy, Id, ToScriptString, ToString } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from "../../util/DragManager";
import { SnappingManager } from "../../util/SnappingManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { LightboxView } from "../LightboxView";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView";
import { StyleProp } from "../StyleProvider";
import "./CollectionNoteTakingView.scss";
import CollectionNoteTakingViewDivider from "./CollectionNoteTakingViewDivider";
import { CollectionNoteTakingViewColumn } from "./CollectionNoteTakingViewColumn";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionViewType } from "./CollectionView";
import { ObjectField } from "../../../fields/ObjectField";
import { faThumbsDown } from "@fortawesome/free-solid-svg-icons";
const _global = (window /* browser */ || global /* node */) as any;

export type collectionNoteTakingViewProps = {
    chromeHidden?: boolean;
    viewType?: CollectionViewType;
    NativeWidth?: () => number;
    NativeHeight?: () => number;
};

//TODO: somehow need to update the mapping and then have everything else rerender. Maybe with a refresh boolean like 
// in Hypermedia?

@observer
export class CollectionNoteTakingView extends CollectionSubView<Partial<collectionNoteTakingViewProps>>() {
    _pivotFieldDisposer?: IReactionDisposer;
    _autoHeightDisposer?: IReactionDisposer;
    _masonryGridRef: HTMLDivElement | null = null;
    _draggerRef = React.createRef<HTMLDivElement>();
    // _docXfs: { height: () => number, width: () => number, noteTakingDocTransform: () => Transform }[] = [];
    // @observable _docsByColumnHeader = new Map<string, Doc[]>();
    //TODO: need to make sure that we save the mapping
    @observable docsDraggedRowCol: number[] = [];
    @observable _cursor: CursorProperty = "grab";
    @observable _scroll = 0; // used to force the document decoration to update when scrolling
    @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); }
    @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); }
    @computed get pivotField() { return "Col" }
    @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); }
    @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); }
    @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
    @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); }
    @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
    @computed get numGroupColumns() { return this.columnHeaders.length; }
    @observable columnStartXCoords: number[] = []
    @computed get PanelWidth() {return this.props.PanelWidth()}
    @computed get maxColWdith() {return this.props.PanelWidth() - 2 * this.xMargin;}

    // If the user has not yet created any docs (in another view), this will create a single column. Otherwise, 
    // it will adjust according to the 
    constructor(props: any) {
        super(props);
        if (this.columnHeaders === undefined) {
          this.layoutDoc._columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column')]);
          this.columnStartXCoords = [0]
          // add all of the docs that have not been added to a column to this new column
        } else {
          const numHeaders = this.columnHeaders.length
          this.resizeColumns(numHeaders)
        }
    }

    // passed as a prop to the NoteTakingField, which uses this function
    // to render the docs you see within an individual column.
    children = (docs: Doc[]) => {
        TraceMobx();
        return docs.map((d, i) => {
            const height = () => this.getDocHeight(d);
            const width = () => this.getDocWidth(d);
            const style = { width: width(), marginTop: this.gridGap, height: height() };
            return <div className={`collectionNoteTakingView-columnDoc`} key={d[Id]} style={style} >
                {this.getDisplayDoc(d, width)}
            </div>;
        });
    }

    // [CAVEATS] (1) keep track of the offsetting
    // (2) documentView gets unmounted as you remove it from the list
    get Sections() {
        const columnHeaders = this.columnHeaders;
        const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
        let docs = this.childDocs
        const rowCol = this.docsDraggedRowCol
        
        // filter out the currently dragged docs from the child docs, since we will insert them later
        if (rowCol.length && DragManager.docsBeingDragged.length) {
          const docIdsToRemove = new Set()
          DragManager.docsBeingDragged.forEach(d => {
            docIdsToRemove.add(d[Id])
          })
          docs = docs.filter(d => !docIdsToRemove.has(d[Id]))
        }

        // this will sort the docs into the correct columns (minus the ones you're currently dragging)
        docs.map(d => {
          if (!d[this.pivotField]) {
            d[this.pivotField] = columnHeaders.length > 0 ? columnHeaders[0].heading : `New Column`
          };
          const sectionValue = d[this.pivotField] as object;

          // look for if header exists already
          const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString());
          if (existingHeader) {
              sections.get(existingHeader)!.push(d);
          }
        });

        // now we add back in the docs that we're dragging
        if (rowCol.length && DragManager.docsBeingDragged.length) {
          const colHeader = columnHeaders[rowCol[1]]
          // TODO: get the actual offset that occurs if the docs were in that column
          const offset = 0
          sections.get(colHeader)?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged)
        }
        return sections;
    }

    componentDidMount() {
        super.componentDidMount?.();
        // reset section headers when a new filter is inputted
        this._pivotFieldDisposer = reaction(
            () => this.pivotField,
            () => this.layoutDoc._columnHeaders = new List()
        );

        this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight,
            autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
                this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))))));
    }

    componentWillUnmount() {
        super.componentWillUnmount();
        this._pivotFieldDisposer?.();
        this._autoHeightDisposer?.();
    }

    @action
    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
        return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
    }

    createRef = (ele: HTMLDivElement | null) => {
        this._masonryGridRef = ele;
        this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
    }

    @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
    @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }

    addDocTab = (doc: Doc, where: string) => {
        if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
            this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
            return true;
        }
        return this.props.addDocTab(doc, where);
    }

    scrollToBottom = () => {
        smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight);
    }

    // let's dive in and get the actual document we want to drag/move around
    focusDocument = (doc: Doc, options?: DocFocusOptions) => {
        Doc.BrushDoc(doc);

        let focusSpeed = 0;
        const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]);
        if (found) {
            const top = found.getBoundingClientRect().top;
            const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
            if (Math.floor(localTop[1]) !== 0) {
                smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
            }
        }
        const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing;
        this.props.focus(this.rootDoc, {
            willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) =>
                new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))
        });
    }

    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
        if (property === StyleProp.Opacity && doc) {
            if (this.props.childOpacity) {
                return this.props.childOpacity();
            }
            if (this.Document._currentFrame !== undefined) {
                return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
            }
        }
        return this.props.styleProvider?.(doc, props, property);
    }

    isContentActive = () => this.props.isSelected() || this.props.isContentActive();

    // rules for displaying the documents
    getDisplayDoc(doc: Doc, width: () => number) {
        const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc;
        const height = () => this.getDocHeight(doc);
        let dref: Opt<DocumentView>;
        const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
        return <DocumentView ref={r => dref = r || undefined}
            Document={doc}
            DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
            renderDepth={this.props.renderDepth + 1}
            PanelWidth={width}
            PanelHeight={height}
            styleProvider={this.styleProvider}
            layerProvider={this.props.layerProvider}
            docViewPath={this.props.docViewPath}
            fitWidth={this.props.childFitWidth}
            isContentActive={emptyFunction}
            originalBackgroundColor={StrCast(doc.backgroundColor)}
            //TODO: change this from a prop to a parameter passed into a function
            isNoteTakingView={true}
            isDocumentActive={this.isContentActive}
            LayoutTemplate={this.props.childLayoutTemplate}
            LayoutTemplateString={this.props.childLayoutString}
            freezeDimensions={this.props.childFreezeDimensions}
            NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined}  // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
            NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined}
            dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined}
            dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
            rootSelected={this.rootSelected}
            showTitle={this.props.childShowTitle}
            dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
            onClick={this.onChildClickHandler}
            onDoubleClick={this.onChildDoubleClickHandler}
            ScreenToLocalTransform={noteTakingDocTransform}
            focus={this.focusDocument}
            docFilters={this.childDocFilters}
            hideDecorationTitle={this.props.childHideDecorationTitle?.()}
            hideResizeHandles={this.props.childHideResizeHandles?.()}
            hideTitle={this.props.childHideTitle?.()}
            docRangeFilters={this.childDocRangeFilters}
            searchFilterDocs={this.searchFilterDocs}
            ContainingCollectionDoc={this.props.CollectionView?.props.Document}
            ContainingCollectionView={this.props.CollectionView}
            addDocument={this.props.addDocument}
            moveDocument={this.props.moveDocument}
            removeDocument={this.props.removeDocument}
            contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)}
            whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
            addDocTab={this.addDocTab}
            bringToFront={returnFalse}
            scriptContext={this.props.scriptContext}
            pinToPres={this.props.pinToPres}
        />;
    }

    // This is used to get the coordinates of a document when we go from a view like freeform to columns
    getDocTransform(doc: Doc, dref?: DocumentView) {
        const y = this._scroll; // required for document decorations to update when the text box container is scrolled
        const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
        // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off 
        return new Transform(- translateX + (dref?.centeringX || 0), - translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
    }

    // how to get the width of a document. Currently returns the width of the column (minus margins)
    // if a note doc. Otherwise, returns the normal width (for graphs, images, etc...)
    getDocWidth(d: Doc) {
      const heading = d[this.pivotField] as object
      const castedSectionValue = heading.toString()
      const existingHeader = this.columnHeaders.find(sh => sh.heading === (castedSectionValue));
      const colStartXCoords = this.columnStartXCoords
      if (!existingHeader) {
        return 1000
      }
      const index = this.columnHeaders.indexOf(existingHeader)
      const endColValue = index == this.columnHeaders.length - 1 ? this.PanelWidth : this.columnStartXCoords[index+1]
      const maxWidth = endColValue - colStartXCoords[index] - 3 * this.xMargin
      if (d.type === DocumentType.RTF) {
        return maxWidth
      }
      const width = d[WidthSym]()
      return width < maxWidth ? width : maxWidth
    }

    // how to get the height of a document. Nothing special here.
    getDocHeight(d?: Doc) {
        if (!d || d.hidden) return 0;
        const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
        const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc;
        const maxHeight = (lim => lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim)(NumCast(this.layoutDoc.childLimitHeight, -1));
        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0);
        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0);
        if (nw && nh) {
            // const colWid = this.columnWidth / this.numGroupColumns;
            // const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
            const docWid = this.getDocWidth(d)
            return Math.min(
                maxHeight,
                docWid * nh / nw);
        }
        const childHeight = NumCast(childLayoutDoc._height);
        const panelHeight = (childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
        return Math.min(childHeight, maxHeight, panelHeight);
    }

    // called when a column is either added or deleted. This function creates n evenly spaced columns
    resizeColumns = (n: number) => {
      const totalWidth = this.PanelWidth
      const dividerWidth = 32
      const totaldividerWidth = (n - 1) * dividerWidth
      const colWidth = (totalWidth - totaldividerWidth)  / n
      const newColXCoords: number[] = []
      let colStart = 0
      for (let i = 0; i < n; i++) {
          newColXCoords.push(colStart)
          colStart += colWidth + dividerWidth
      }
      this.columnStartXCoords = newColXCoords
    }

    // This function is used to preview where a document will drop in a column once a drag is complete.
    @action
    onPointerOver = (e: React.PointerEvent) => {
      if (DragManager.docsBeingDragged.length && this.childDocList) {
        // get the current docs for the column based on the mouse's x coordinate
        // will use again later, which is why we're saving as local
        const xCoord = e.clientX - 2 * this.gridGap
        const colDocs = this.getDocsFromXCoord(xCoord)
        // get the index for where you need to insert the doc you are currently dragging
        const clientY = e.clientY
        let dropInd = -1;
        // unsure whether we still want this dropAfter field
        // let dropAfter = 0;
        // manually set to 140, because not sure how to get exact value
        let pos0 = 140
        colDocs.forEach((doc, i) => {
          const noteTakingDocTransform = () => this.getDocTransform(doc);
          let pos1 = noteTakingDocTransform().inverse().transformPoint(0, this.getDocHeight(doc) + 2 * this.gridGap)[1];
          pos1 += pos0
          // updating drop position based on y coordinates
          const yCoordInBetween = clientY > pos0 && (clientY < pos1 || i == colDocs.length - 1)
          if (yCoordInBetween) {
            dropInd = i;
            // dropAfter = 0;
            if (clientY > (pos0 + pos1) / 2) {
              // dropAfter = 1;
            }
          }
          pos0 = pos1
        })
        // we alter the pivot fields of the docs in case they are moved to a new column.
        const colIndex = this.getColumnFromXCoord(xCoord)
        const colHeader = StrCast(this.columnHeaders[colIndex].heading)
        DragManager.docsBeingDragged.forEach(d => d[this.pivotField] = colHeader)
        // used to notify sections to re-render
        // console.log([dropInd, this.getColumnFromXCoord(xCoord)])
        this.docsDraggedRowCol = [dropInd, this.getColumnFromXCoord(xCoord)]
      }
    }

    // returns the column index for a given x-coordinate
    getColumnFromXCoord = (xCoord: number): number => {
        const numColumns = this.columnHeaders.length
        const coords = this.columnStartXCoords.slice()
        coords.push(this.PanelWidth)
        let colIndex = 0
        for (let i = 0; i < numColumns; i++) {
            if (xCoord > coords[i] && xCoord < coords[i + 1]) {
              colIndex = i
              break
            }
        }
        return colIndex
    }

    // returns the docs of a column based on the x-coordinate provided. 
    getDocsFromXCoord = (xCoord: number): Doc[] => {
      const colIndex = this.getColumnFromXCoord(xCoord)
      const colHeader = StrCast(this.columnHeaders[colIndex].heading)
      // const docs = this.childDocList
      const docs = this.childDocs
      const docsMatchingHeader: Doc[] = []
      if (docs) {
        docs.map(d => {
          if (d instanceof Promise) return;
          const sectionValue = d[this.pivotField] as object;
          if (sectionValue.toString() == colHeader) {
            docsMatchingHeader.push(d)
          }
        })
      }
      return docsMatchingHeader;
    }

    @undoBatch
    @action
    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
        if (de.complete.docDragData) {
            if (super.onInternalDrop(e, de)) {
              DragManager.docsBeingDragged = []
              // this.docsDraggedRowCol = []
              // filter out the currently dragged docs from the child docs, since we will insert them later
              const rowCol = this.docsDraggedRowCol
              const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
              const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments;
              
              // const docs = this.childDocs
              const docs = this.childDocList
              if (docs && newDocs.length) {
                // remove the dragged documents from the childDocList
                newDocs.filter(d => docs.indexOf(d) !== -1).forEach(d => docs.splice(docs.indexOf(d), 1))
                // if the doc starts a columnm (or the drop index is undefined), we can just push it to the front. Otherwise we need to add it to the column properly
                //TODO: you need to update childDocList instead. It seems that childDocs is a copy of the actual array we want to modify
                if (rowCol[0] <= 0) {
                  docs.splice(0, 0, ...newDocs)
                } else {
                  const colDocs = this.getDocsFromXCoord(de.x)
                  const previousDoc = colDocs[rowCol[0] - 1]
                  const previousDocIndex = docs.indexOf(previousDoc)
                  console.log(`docs: ${previousDocIndex}`)
                  docs.splice(previousDocIndex + 1, 0, ...newDocs)
                }
              }
            }
        } // it seems like we're creating a link here. Weird. I didn't know that you could establish links by dragging
        else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
            const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, _fitWidth: true, title: "dropped annotation" });
            this.props.addDocument?.(source);
            de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed
            e.stopPropagation();
        }
        else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
        return false;
    }

    @undoBatch
    internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) {
        const dropCreator = annoDragData.dropDocCreator;
        annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => {
            const dropDoc = dropCreator(annotationOn);
            return dropDoc || this.rootDoc;
        };
        return true;
    }

    // when dropping outside of the current noteTaking context (like a new tab, freeform view, etc...)
    @undoBatch
    @action
    onExternalDrop = async (e: React.DragEvent): Promise<void> => {
        const where = [e.clientX, e.clientY];
        let targInd = -1;
        const docs = this.getDocsFromXCoord(where[0])
        docs.map((d, i) => {
          const pos0 = this.getDocTransform(d).inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
          const pos1 = this.getDocTransform(d).inverse().transformPoint(this.getDocWidth(d), this.getDocHeight(d));
          // const pos0 = cd.noteTakingDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
          // const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(cd.width(), cd.height());
          if (where[0] > pos0[0] && where[0] < pos1[0] && where[1] > pos0[1] && where[1] < pos1[1]) {
              targInd = i;
          }
        })
        // this._docXfs.map((cd, i) => {
        //     const pos = cd.noteTakingDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
        //     const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(cd.width(), cd.height());
        //     if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
        //         targInd = i;
        //     }
        // });
        super.onExternalDrop(e, {}, () => {
            if (targInd !== -1) {
                const newDoc = this.childDocs[this.childDocs.length - 1];
                const docs = this.childDocList;
                if (docs) {
                    docs.splice(docs.length - 1, 1);
                    docs.splice(targInd, 0, newDoc);
                }
            }
        });
    }

    // setDocsForColHeader = (key: string, docs: Doc[]) => {
    //   this._docsByColumnHeader = new Map(this._docsByColumnHeader.set(key, docs))
    // }

    headings = () => Array.from(this.Sections);

    refList: any[] = [];

    sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
        const type = "number";
        return <CollectionNoteTakingViewColumn
            unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
            observeHeight={ref => {
                if (ref) {
                    this.refList.push(ref);
                    this.observer = new _global.ResizeObserver(action((entries: any) => {
                        if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                            const height = this.headerMargin +
                                Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
                                    Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
                            if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
                                this.props.setHeight(height);
                            }
                        }
                    }));
                    this.observer.observe(ref);
                }
            }}
            addDocument={this.addDocument}
            // docsByColumnHeader={this._docsByColumnHeader}
            // setDocsForColHeader={this.setDocsForColHeader}
            chromeHidden={this.chromeHidden}
            columnHeaders={this.columnHeaders}
            Document={this.props.Document}
            DataDoc={this.props.DataDoc}
            resizeColumns={this.resizeColumns.bind(this)}
            renderChildren={this.children}
            numGroupColumns={this.numGroupColumns}
            gridGap={this.gridGap}
            pivotField={this.pivotField}
            columnStartXCoords={this.columnStartXCoords}
            maxColWidth={this.maxColWdith}
            PanelWidth={this.PanelWidth}
            key={heading?.heading ?? ""}
            headings={this.headings}
            heading={heading?.heading ?? ""}
            headingObject={heading}
            docList={docList}
            yMargin={this.yMargin}
            type={type}
            createDropTarget={this.createDashEventsTarget}
            screenToLocalTransform={this.props.ScreenToLocalTransform}
            editableViewProps={{
              GetValue: () => "",
              SetValue: this.addGroup,
              contents: "+ New Column"
          }}
        />;
    }

    // called when adding a new columnHeader
    @action
    addGroup = (value: string) => {
        if (value && this.columnHeaders) {
            this.resizeColumns(this.columnHeaders.length + 1)
            const schemaHdrField = new SchemaHeaderField(value);
            this.columnHeaders.push(schemaHdrField);
            DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: "schemaHdrField.color" }]);
            return true;
        }
        return false;
    }

    sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => {
        const descending = StrCast(this.layoutDoc._columnsSort) === "descending";
        const firstEntry = descending ? b : a;
        const secondEntry = descending ? a : b;
        return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1;
    }

    onContextMenu = (e: React.MouseEvent): void => {
        // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
        if (!e.isPropagationStopped()) {
            const subItems: ContextMenuProps[] = [];
            subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" });
            subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
            subItems.push({ description: "Clear All", event: () => this.dataDoc.data = new List([]), icon: "times" });
            ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
        }
    }

    // used to reset column sizes when using the drag handlers
    @action
    setColumnStartXCoords = (movementX: number, colIndex: number) => {
      const coords = [...this.columnStartXCoords]
      coords[colIndex] += movementX
      this.columnStartXCoords = coords
    }

    @computed get renderedSections() {
        TraceMobx();
        // let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
        // if (this.pivotField) {
        //     const entries = Array.from(this.Sections.entries());
        //     sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries;
        // }
        const entries = Array.from(this.Sections.entries());
        const sections = entries.sort(this.sortFunc)
        const eles: JSX.Element[] = []
        for (let i = 0; i < sections.length; i++) {
            const col = this.sectionNoteTaking(sections[i][0], sections[i][1])
            eles.push(col)
            if (i < sections.length - 1) {
              eles.push(
                <CollectionNoteTakingViewDivider
                  index={i + 1}
                  setColumnStartXCoords={this.setColumnStartXCoords.bind(this)}
                  xMargin={this.xMargin}
                />
              )
            }
        }
        return eles
    }

    @computed get buttonMenu() {
        const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
        // TODO:glr Allow support for multiple buttons
        if (menuDoc) {
            const width: number = NumCast(menuDoc._width, 30);
            const height: number = NumCast(menuDoc._height, 30);
            return (<div className="buttonMenu-docBtn"
                style={{ width: width, height: height }}>
                <DocumentView
                    Document={menuDoc}
                    DataDoc={menuDoc}
                    isContentActive={this.props.isContentActive}
                    isDocumentActive={returnTrue}
                    addDocument={this.props.addDocument}
                    moveDocument={this.props.moveDocument}
                    addDocTab={this.props.addDocTab}
                    pinToPres={emptyFunction}
                    rootSelected={this.props.isSelected}
                    removeDocument={this.props.removeDocument}
                    ScreenToLocalTransform={Transform.Identity}
                    PanelWidth={() => 35}
                    PanelHeight={() => 35}
                    renderDepth={this.props.renderDepth}
                    focus={emptyFunction}
                    styleProvider={this.props.styleProvider}
                    layerProvider={this.props.layerProvider}
                    docViewPath={returnEmptyDoclist}
                    whenChildContentsActiveChanged={emptyFunction}
                    bringToFront={emptyFunction}
                    docFilters={this.props.docFilters}
                    docRangeFilters={this.props.docRangeFilters}
                    searchFilterDocs={this.props.searchFilterDocs}
                    ContainingCollectionView={undefined}
                    ContainingCollectionDoc={undefined}
                />
            </div>
            );
        }
    }


    @computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); }
    @computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); }

    @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; }

    @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); }
    observer: any;
    render() {
        TraceMobx();
        const buttonMenu = this.rootDoc.buttonMenu;
        const noviceExplainer = this.rootDoc.explainer;
        return (
            <>
                {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
                    {buttonMenu ? this.buttonMenu : null}
                    {Doc.UserDoc().noviceMode && noviceExplainer ?
                        <div className="documentExplanation">
                            {noviceExplainer}
                        </div>
                        : null
                    }
                </div> : null}
                <div className="collectionNoteTakingView"
                    ref={this.createRef}
                    style={{
                        overflowY: this.props.isContentActive() ? "auto" : "hidden",
                        background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
                        pointerEvents: this.backgroundEvents ? "all" : undefined
                    }}
                    onScroll={action(e => this._scroll = e.currentTarget.scrollTop)}
                    onPointerOver={this.onPointerOver}
                    onDrop={this.onExternalDrop.bind(this)}
                    onContextMenu={this.onContextMenu}
                    onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} 
                  >
                    {this.renderedSections}
                </div>
            </>

        );
    }
}