import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popup, Type } from 'browndash-components';
import { clamp } from 'lodash';
import { action, computed, IReactionDisposer, observable, ObservableSet, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom/client';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { FieldId } from '../../../fields/RefField';
import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoable, UndoManager } from '../../util/UndoManager';
import { DashboardView } from '../DashboardView';
import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
import { DashFieldView } from '../nodes/formattedText/DashFieldView';
import { KeyValueBox } from '../nodes/KeyValueBox';
import { PinProps, PresBox, PresMovement } from '../nodes/trails';
import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
import { CollectionDockingView } from './CollectionDockingView';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionView } from './CollectionView';
import './TabDocView.scss';
import React = require('react');
const _global = (window /* browser */ || global) /* node */ as any;

interface TabDocViewProps {
    documentId: FieldId;
    keyValue?: boolean;
    glContainer: any;
}
@observer
export class TabDocView extends React.Component<TabDocViewProps> {
    static _allTabs = new ObservableSet<TabDocView>();
    _mainCont: HTMLDivElement | null = null;
    _tabReaction: IReactionDisposer | undefined;
    @observable _activated: boolean = false;
    @observable _panelWidth = 0;
    @observable _panelHeight = 0;
    @observable _hovering = false;
    @observable _isActive: boolean = false;
    @observable _isAnyChildContentActive = false;
    @computed get _isUserActivated() {
        return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive;
    }
    @computed get _isContentActive() {
        return this._isUserActivated || this._hovering;
    }
    @observable _document: Doc | undefined;
    @observable _view: DocumentView | undefined;

    @computed get layoutDoc() {
        return this._document && Doc.Layout(this._document);
    }
    @computed get tabBorderColor() {
        const highlight = DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting);
        if (highlight?.highlightIndex === Doc.DocBrushStatus.highlighted) return highlight.highlightColor;
        return 'transparent';
    }
    @computed get tabColor() {
        return this._isUserActivated ? Colors.WHITE : this._hovering ? Colors.LIGHT_GRAY : Colors.MEDIUM_GRAY;
    }
    @computed get tabTextColor() {
        return this._document?.type === DocumentType.PRES ? 'black' : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color)));
    }
    // @computed get renderBounds() {
    //     const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
    //     const xbounds = bounds[2] - bounds[0];
    //     const ybounds = bounds[3] - bounds[1];
    //     const dim = Math.max(xbounds, ybounds);
    //     return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
    // }

    get stack() {
        return (this.props as any).glContainer.parent.parent;
    }
    get tab() {
        return (this.props as any).glContainer.tab;
    }
    get view() {
        return this._view;
    }
    _lastTab: any;
    _lastView: DocumentView | undefined;

    @action
    init = (tab: any, doc: Opt<Doc>) => {
        if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true;
        if (tab.DashDoc !== doc && doc && tab.hasOwnProperty('contentItem') && tab.contentItem.config.type !== 'stack') {
            tab._disposers = {} as { [name: string]: IReactionDisposer };
            tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
            tab.DashDoc = doc;
            const iconType: IconProp = Doc.toIcon(doc);
            // setup the title element and set its size according to the # of chars in the title.  Show the full title when clicked.
            const titleEle = tab.titleElement[0];
            const iconWrap = document.createElement('div');
            const closeWrap = document.createElement('div');

            const getChild = () => {
                let child = this.view?.ContentDiv?.children[0];
                while (child?.children.length) {
                    const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
                    if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
                    if (next?.className?.toString().includes(DashFieldView.name)) break;
                    if (next) child = next;
                    else break;
                }
                return child;
            };

            titleEle.size = StrCast(doc.title).length + 3;
            titleEle.value = doc.title;
            titleEle.onkeydown = (e: KeyboardEvent) => e.stopPropagation();
            titleEle.onchange = (e: any) => {
                undoable(() => {
                    titleEle.size = e.currentTarget.value.length + 3;
                    Doc.GetProto(doc).title = e.currentTarget.value;
                }, 'edit tab title')();
            };

            if (tab.element[0].children[1].children.length === 1) {
                iconWrap.className = 'lm_iconWrap lm_moreInfo';
                iconWrap.title = 'click for menu, drag to embed in document';
                const dragBtnDown = (e: React.PointerEvent) => {
                    setupMoveUpEvents(
                        this,
                        e,
                        e =>
                            !e.defaultPrevented &&
                            DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY, undefined, () => {
                                CollectionDockingView.CloseSplit(doc);
                            }),
                        returnFalse,
                        action(e => {
                            if (this.view) {
                                SelectionManager.SelectView(this.view, false);
                                const child = getChild();
                                simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
                            } else {
                                this._activated = true;
                                setTimeout(() => this.view && SelectionManager.SelectView(this.view, false));
                            }
                        })
                    );
                };

                const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
                const closeIcon = <FontAwesomeIcon icon={'eye'} />;
                ReactDOM.createRoot(iconWrap).render(docIcon);
                ReactDOM.createRoot(closeWrap).render(closeIcon);
                tab.reactComponents = [iconWrap, closeWrap];
                tab.element[0].prepend(iconWrap);
                tab._disposers.color = reaction(
                    () => ({ color: this.tabColor, borderColor: this.tabBorderColor }),
                    coloring => {
                        const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color
                        titleEle.style.color = textColor;
                        titleEle.style.backgroundColor = coloring.borderColor;
                        iconWrap.style.color = textColor;
                        closeWrap.style.color = textColor;
                        tab.element[0].style.background = coloring.color;
                    },
                    { fireImmediately: true }
                );
            }
            // shifts the focus to this tab when another tab is dragged over it
            tab.element[0].onmouseenter = (e: MouseEvent) => {
                if (SnappingManager.GetIsDragging() && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
                    tab.header.parent.setActiveContentItem(tab.contentItem);
                    tab.setActive(true);
                }
            };

            tab.element[0].oncontextmenu = (e: MouseEvent) => {
                let child = getChild();
                if (child) {
                    simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
                    e.stopPropagation();
                    e.preventDefault();
                }
            };

            // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected
            titleEle.onpointerdown = action((e: any) => {
                if (e.target.className !== 'lm_iconWrap') {
                    if (this.view) SelectionManager.SelectView(this.view, false);
                    else this._activated = true;
                    if (Date.now() - titleEle.lastClick < 1000) titleEle.select();
                    titleEle.lastClick = Date.now();
                    document.activeElement !== titleEle && titleEle.focus();
                }
            });
            tab._disposers.selectionDisposer = reaction(
                () => SelectionManager.IsSelected(this._document),
                action(selected => {
                    if (selected) this._activated = true;
                    const toggle = tab.element[0].children[2].children[0] as HTMLInputElement;
                    if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
                        undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')();
                    }
                    toggle.style.fontWeight = selected ? 'bold' : '';
                    // toggle.style.textTransform = selected ? "uppercase" : "";
                }),
                { fireImmediately: true }
            );

            // highlight the tab when the tab document is brushed in any part of the UI
            // tab._disposers.reactionDisposer = reaction(
            //     () => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }),
            //     ({ title, degree }) => {
            //         titleEle.value = title;
            //         titleEle.style.padding = degree ? 0 : 2;
            //         titleEle.style.border = `${['gray', 'gray', 'gray'][degree]} ${['none', 'dashed', 'solid'][degree]} 2px`;
            //     },
            //     { fireImmediately: true }
            // );

            // clean up the tab when it is closed
            tab.closeElement
                .off('click') //unbind the current click handler
                .click(function () {
                    Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
                    SelectionManager.DeselectAll();
                    UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab');
                });
        }
    };

    /**
     * Adds a document to the presentation view
     **/
    @action
    public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) {
        const docList = docs instanceof Doc ? [docs] : docs;

        const batch = UndoManager.StartBatch('Pin doc to pres trail');
        const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);

        if (!Doc.ActivePresentation) {
            Doc.AddDocToList(Doc.MyTrails, 'data', curPres);
            Doc.ActivePresentation = curPres;
        }

        docList.forEach(doc => {
            // Edge Case 1: Cannot pin document to itself
            if (doc === curPres) {
                alert('Cannot pin presentation document to itself');
                return;
            }
            const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
            const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc);
            pinDoc.presentation_targetDoc = anchorDoc ?? doc;
            pinDoc.title = doc.title + ' - Slide';
            pinDoc.data = new List<Doc>(); // the children of the embedding's layout are the presentation slide children.  the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
            pinDoc.presentation_movement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
            pinDoc.presentation_duration = pinDoc.presentation_duration ?? 1000;
            pinDoc.presentation_groupWithUp = false;
            Doc.SetContainer(pinDoc, curPres);
            // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements.  That way the PresBox could configure all of its children at render time
            pinDoc.treeView = ''; // not really needed, but makes key value pane look better
            pinDoc.treeView_RenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
            pinDoc.treeView_HeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling.
            pinDoc.treeView_ChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
            pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
            pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
            pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
            const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null);

            if (pinProps.pinViewport) PresBox.pinDocView(pinDoc, pinProps, anchorDoc ?? doc);
            if (!pinProps?.audioRange && duration !== undefined) {
                pinDoc.presentation_mediaStart = 'manual';
                pinDoc.presentation_mediaStop = 'manual';
            }
            if (pinProps?.activeFrame !== undefined) {
                pinDoc.config_activeFrame = pinProps?.activeFrame;
                pinDoc.title = doc.title + ' (move)';
                pinDoc.presentation_movement = PresMovement.Pan;
            }
            if (pinProps?.currentFrame !== undefined) {
                pinDoc.config_currentFrame = pinProps?.currentFrame;
                pinDoc.title = doc.title + ' (move)';
                pinDoc.presentation_movement = PresMovement.Pan;
            }
            if (pinDoc.stroke_isInkMask) {
                pinDoc.presentation_hideAfter = true;
                pinDoc.presentation_hideBefore = true;
                pinDoc.presentation_movement = PresMovement.None;
            }
            if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true;
            Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement());
            PresBox.Instance?.clearSelectedArray();
            pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array
        });
        if (
            // open the presentation trail if it's not already opened
            !Array.from(CollectionDockingView.Instance?.tabMap ?? [])
                .map(d => d.DashDoc)
                .includes(curPres)
        ) {
            if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres);
            CollectionDockingView.AddSplit(curPres, OpenWhereMod.right);
            setTimeout(() => DocumentManager.Instance.showDocument(docList.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things
        }
        setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
    }

    @action
    componentDidMount() {
        new _global.ResizeObserver(
            action((entries: any) => {
                for (const entry of entries) {
                    this._panelWidth = entry.contentRect.width;
                    this._panelHeight = entry.contentRect.height;
                }
            })
        ).observe(this.props.glContainer._element[0]);
        this.props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged);
        this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
        // this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
        //     ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
        //     { fireImmediately: true });
        TabDocView._allTabs.add(this);
    }
    componentDidUpdate() {
        this._view && DocumentManager.Instance.AddView(this._view);
    }

    @action
    componentWillUnmount() {
        this._tabReaction?.();
        this._view && DocumentManager.Instance.RemoveView(this._view);
        TabDocView._allTabs.delete(this);

        this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
    }

    @action.bound
    private onActiveContentItemChanged(contentItem: any) {
        if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
            this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
            if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== 'dontSelectOnActivate') setTimeout(() => SelectionManager.SelectView(this._view, false));
            !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
        }
    }

    // adds a tab to the layout based on the locaiton parameter which can be:
    //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab,
    //  add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right
    //  replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents,
    //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
    //                                   "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
    //  lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox
    addDocTab = (doc: Doc, location: OpenWhere) => {
        SelectionManager.DeselectAll();
        const whereFields = location.split(':');
        const keyValue = whereFields[1]?.includes('KeyValue');
        const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
        if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
        // prettier-ignore
        switch (whereFields[0]) {
            case undefined:
            case OpenWhere.lightbox:   if (this.layoutDoc?._isLightbox) {
                                            const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : undefined;
                                            const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)];
                                            if (lightboxView && (!data || data instanceof List)) {
                                                lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)] = new List<Doc>([doc]);
                                                return true;
                                            }
                                       }
                                       return LightboxView.AddDocTab(doc, location);
            case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue);
            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, "dontSelectOnActivate", keyValue);
            case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue);
        }
    };
    remDocTab = (doc: Doc | Doc[]) => {
        if (doc === this._document) {
            SelectionManager.DeselectAll();
            CollectionDockingView.CloseSplit(this._document);
            return true;
        }
        return false;
    };

    getCurrentFrame = () => {
        return NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
    };
    static Activate = (tabDoc: Doc) => {
        const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc && !tab.contentItem.config.props.keyValue);
        tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
        return tab !== undefined;
    };
    @action
    focusFunc = (doc: Doc, options: DocFocusOptions) => {
        if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
            this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
        }
        return undefined;
    };
    active = () => this._isActive;
    @observable _forceInvalidateScreenToLocal = 0;
    ScreenToLocalTransform = () => {
        this._forceInvalidateScreenToLocal;
        const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
        return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
    };
    PanelWidth = () => this._panelWidth;
    PanelHeight = () => this._panelHeight;
    miniMapColor = () => Colors.MEDIUM_GRAY;
    tabView = () => this._view;
    disableMinimap = () => !this._document;
    whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive);
    isContentActive = () => this._isContentActive;
    waitForDoubleClick = () => (DocumentView.ExploreMode ? 'never' : undefined);
    @computed get docView() {
        return !this._activated || !this._document ? null : (
            <>
                <DocumentView
                    key={this._document[Id]}
                    ref={action((r: DocumentView) => {
                        this._lastView && DocumentManager.Instance.RemoveView(this._lastView);
                        this._view = r;
                        this._lastView = this._view;
                    })}
                    renderDepth={0}
                    LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
                    hideTitle={this.props.keyValue}
                    Document={this._document}
                    DataDoc={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
                    onBrowseClick={DocumentView.exploreMode}
                    waitForDoubleClickToClick={this.waitForDoubleClick}
                    isContentActive={this.isContentActive}
                    isDocumentActive={returnFalse}
                    PanelWidth={this.PanelWidth}
                    PanelHeight={this.PanelHeight}
                    styleProvider={DefaultStyleProvider}
                    childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
                    childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                    addDocument={undefined}
                    removeDocument={this.remDocTab}
                    addDocTab={this.addDocTab}
                    ScreenToLocalTransform={this.ScreenToLocalTransform}
                    dontCenter={'y'}
                    rootSelected={returnTrue}
                    whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
                    focus={this.focusFunc}
                    docViewPath={returnEmptyDoclist}
                    bringToFront={emptyFunction}
                    pinToPres={TabDocView.PinDoc}
                />
                {this.disableMinimap() ? null : <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />}
            </>
        );
    }

    render() {
        return (
            <div
                className="tabDocView-content"
                style={{
                    fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined,
                }}
                onPointerOver={action(() => (this._hovering = true))}
                onPointerLeave={action(() => (this._hovering = false))}
                onDragOver={action(() => (this._hovering = true))}
                onDragLeave={action(() => (this._hovering = false))}
                ref={ref => {
                    if ((this._mainCont = ref)) {
                        if (this._lastTab) {
                            this._view && DocumentManager.Instance.RemoveView(this._view);
                        }
                        this._lastTab = this.tab;
                        (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
                        DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
                        new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref);
                    }
                }}>
                {this.docView}
            </div>
        );
    }
}

interface TabMinimapViewProps {
    document: Doc;
    tabView: () => DocumentView | undefined;
    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
    PanelWidth: () => number;
    PanelHeight: () => number;
    background: () => string;
}
interface TabMiniThumbProps {
    miniWidth: () => number;
    miniHeight: () => number;
    miniTop: () => number;
    miniLeft: () => number;
}
@observer
class TabMiniThumb extends React.Component<TabMiniThumbProps> {
    render() {
        return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />;
    }
}
@observer
export class TabMinimapView extends React.Component<TabMinimapViewProps> {
    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
        if (doc) {
            switch (property.split(':')[0]) {
                default:
                    return DefaultStyleProvider(doc, props, property);
                case StyleProp.PointerEvents:
                    return 'none';
                case StyleProp.DocContents:
                    const background = ((type: DocumentType) => {
                        // prettier-ignore
                        switch (type) {
                            case DocumentType.PDF:    return 'pink';
                            case DocumentType.AUDIO:  return 'lightgreen';
                            case DocumentType.WEB:    return 'brown';
                            case DocumentType.IMG:    return 'blue';
                            case DocumentType.MAP:    return 'orange';
                            case DocumentType.VID:    return 'purple';
                            case DocumentType.RTF:    return 'yellow';
                            case DocumentType.COL:    return undefined;
                            default:                  return 'gray';
                        }
                    })(doc.type as DocumentType);
                    return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />;
            }
        }
    };
    @computed get renderBounds() {
        const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView;
        const bounds = compView?.freeformData?.(true)?.bounds;
        if (!bounds) return undefined;
        const xbounds = bounds.r - bounds.x;
        const ybounds = bounds.b - bounds.y;
        const dim = Math.max(xbounds, ybounds);
        return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
    }
    childLayoutTemplate = () => Cast(this.props.document.childLayoutTemplate, Doc, null);
    returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150);
    miniDown = (e: React.PointerEvent) => {
        const doc = this.props.document;
        const miniSize = this.returnMiniSize();
        doc &&
            setupMoveUpEvents(
                this,
                e,
                action((e: PointerEvent, down: number[], delta: number[]) => {
                    const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
                    doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
                    doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
                    return false;
                }),
                emptyFunction,
                emptyFunction
            );
    };
    popup = () => {
        if (!this.renderBounds) return <></>;
        const renderBounds = this.renderBounds;
        const miniWidth = () => (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
        const miniHeight = () => (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
        const miniLeft = () => 50 + ((NumCast(this.props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
        const miniTop = () => 50 + ((NumCast(this.props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
        const miniSize = this.returnMiniSize();
        return (
            <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
                <CollectionFreeFormView
                    Document={this.props.document}
                    docViewPath={returnEmptyDoclist}
                    childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                    noOverlay={true} // don't render overlay Docs since they won't scale
                    setHeight={returnFalse}
                    isContentActive={emptyFunction}
                    isAnyChildContentActive={returnFalse}
                    select={emptyFunction}
                    isSelected={returnFalse}
                    dontRegisterView={true}
                    fieldKey={Doc.LayoutFieldKey(this.props.document)}
                    bringToFront={emptyFunction}
                    rootSelected={returnTrue}
                    addDocument={returnFalse}
                    moveDocument={returnFalse}
                    removeDocument={returnFalse}
                    PanelWidth={this.returnMiniSize}
                    PanelHeight={this.returnMiniSize}
                    ScreenToLocalTransform={Transform.Identity}
                    renderDepth={0}
                    whenChildContentsActiveChanged={emptyFunction}
                    focus={emptyFunction}
                    styleProvider={TabMinimapView.miniStyleProvider}
                    addDocTab={this.props.addDocTab}
                    pinToPres={TabDocView.PinDoc}
                    childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
                    childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                    fitContentsToBox={returnTrue}
                />
                <div className="miniOverlay" onPointerDown={this.miniDown}>
                    <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
                </div>
            </div>
        );
    };
    render() {
        return this.props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this.props.document)) || this.props.document?._type_collection !== CollectionViewType.Freeform ? null : (
            <div className="miniMap-hidden">
                <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement={'top-end'} popup={this.popup} />
            </div>
        );
    }
}
