import { library } from '@fortawesome/fontawesome-svg-core';
import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
import * as far from '@fortawesome/free-regular-svg-icons';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import 'browndash-components/dist/styles/global.min.css';
import { action, computed, configure, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import 'normalize.css';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { DocCast, StrCast } from '../../fields/Types';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs } from '../documents/Documents';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { CaptureManager } from '../util/CaptureManager';
import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
import { ReportManager } from '../util/reportManager/ReportManager';
import { RTFMarkup } from '../util/RTFMarkup';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { SelectionManager } from '../util/SelectionManager';
import { ServerStats } from '../util/ServerStats';
import { ColorScheme, SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
import { CollectionLinearView } from './collections/collectionLinear';
import { CollectionMenu } from './collections/CollectionMenu';
import './collections/TreeView.scss';
import { ComponentDecorations } from './ComponentDecorations';
import { ContextMenu } from './ContextMenu';
import { DashboardView } from './DashboardView';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
import { GestureOverlay } from './GestureOverlay';
import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss';
import { KeyManager } from './GlobalKeyHandler';
import { InkTranscription } from './InkTranscription';
import { LightboxView } from './LightboxView';
import { LinkMenu } from './linking/LinkMenu';
import './MainView.scss';
import { AudioBox } from './nodes/AudioBox';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
import GenerativeFill from './nodes/generativeFill/GenerativeFill';
import { ImageBox } from './nodes/ImageBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview } from './nodes/LinkDocPreview';
import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
import { MapBox } from './nodes/MapBox/MapBox';
import { RadialMenu } from './nodes/RadialMenu';
import { TaskCompletionBox } from './nodes/TaskCompletedBox';
import { OverlayView } from './OverlayView';
import { AnchorMenu } from './pdf/AnchorMenu';
import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
import { TopBar } from './topbar/TopBar';
const _global = (window /* browser */ || global) /* node */ as any;

@observer
export class MainView extends React.Component {
    public static Instance: MainView;
    public static Live: boolean = false;
    private _docBtnRef = React.createRef<HTMLDivElement>();

    @observable private _windowWidth: number = 0;
    @observable private _windowHeight: number = 0;
    @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
    @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
    @observable private _panelContent: string = 'none';
    @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel;
    @observable private _leftMenuFlyoutWidth: number = 0;
    @computed get _hideUI() {
        return this.mainDoc && this.mainDoc._type_collection !== CollectionViewType.Docking;
    }

    @computed private get dashboardTabHeight() {
        return this._hideUI ? 0 : 27;
    } //  27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
    @computed private get topOfDashUI() {
        return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(TOPBAR_HEIGHT.replace('px', ''));
    }
    @computed private get topOfHeaderBarDoc() {
        return this.topOfDashUI;
    }
    @computed private get topOfSidebarDoc() {
        return this.topOfDashUI + this.topMenuHeight();
    }
    @computed private get topOfMainDoc() {
        return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight();
    }
    @computed private get topOfMainDocContent() {
        return this.topOfMainDoc + this.dashboardTabHeight;
    }
    @computed private get leftScreenOffsetOfMainDocView() {
        return this.leftMenuWidth() - 2;
    }
    @computed private get userDoc() {
        return Doc.UserDoc();
    }
    @computed private get colorScheme() {
        return StrCast(Doc.ActiveDashboard?.colorScheme);
    }
    @observable mainDoc: Opt<Doc>;
    @computed private get mainContainer() {
        if (window.location.pathname.startsWith('/doc/') && Doc.CurrentUserEmail === 'guest') {
            DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
            return this.mainDoc;
        }
        return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard;
    }
    @computed private get headerBarDoc() {
        return Doc.MyHeaderBar;
    }
    @computed public get mainFreeform(): Opt<Doc> {
        return (docs => (docs?.length > 1 ? docs[1] : undefined))(DocListCast(this.mainContainer!.data));
    }

    headerBarDocWidth = () => this.mainDocViewWidth();
    headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.headerBarHeight ?? 0);
    topMenuHeight = () => (this._hideUI ? 0 : 35);
    topMenuWidth = returnZero; // value is ignored ...
    leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
    leftMenuHeight = () => this._dashUIHeight;
    leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
    leftMenuFlyoutHeight = () => this._dashUIHeight;
    propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.propertiesWidth || 0));
    propertiesHeight = () => this._dashUIHeight;
    mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
    mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();

    componentDidMount() {
        document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!));
        const ele = document.getElementById('loader');
        const prog = document.getElementById('dash-progress');
        if (ele && prog) {
            // remove from DOM
            setTimeout(() => {
                prog.style.transition = '1s';
                prog.style.width = '100%';
            }, 0);
            setTimeout(() => (ele.outerHTML = ''), 1000);
        }
        this._sidebarContent.proto = undefined;
        if (!MainView.Live) {
            DocServer.setLivePlaygroundFields([
                'dataTransition',
                'viewTransition',
                'treeView_Open',
                'treeView_ExpandedView',
                'carousel_index',
                'itemIndex', // for changing slides in presentations
                'layout_sidebarWidthPercent',
                'layout_currentTimecode',
                'layout_timelineHeightPercent',
                'layout_hideMinimap',
                'layout_showSidebar',
                'layout_scrollTop',
                'layout_fitWidth',
                'layout_curPage',
                'presStatus',
                'freeform_panX',
                'freeform_panY',
                'freeform_scale',
                'overlayX',
                'overlayY',
                'text_scrollHeight',
                'text_height',
                'hidden',
                //'type_collection',
                'chromeHidden',
                'currentFrame',
            ]); // can play with these fields on someone else's
        }

        const tag = document.createElement('script');
        tag.src = 'https://www.youtube.com/iframe_api';
        const firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
        window.removeEventListener('keydown', KeyManager.Instance.handle);
        window.addEventListener('keydown', KeyManager.Instance.handle);
        window.removeEventListener('keyup', KeyManager.Instance.unhandle);
        window.addEventListener('keyup', KeyManager.Instance.unhandle);
        window.addEventListener('paste', KeyManager.Instance.paste as any);
        document.addEventListener('dash', (e: any) => {
            // event used by chrome plugin to tell Dash which document to focus on
            const id = FormattedTextBox.GetDocFromUrl(e.detail);
            DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.showDocument(doc, { willPan: false }) : null));
        });
        document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
        this.initEventListeners();
    }

    componentWillUnMount() {
        window.removeEventListener('keyup', KeyManager.Instance.unhandle);
        window.removeEventListener('keydown', KeyManager.Instance.handle);
        window.removeEventListener('pointerdown', this.globalPointerDown, true);
        window.removeEventListener('pointermove', this.globalPointerMove, true);
        window.removeEventListener('pointerup', this.globalPointerClick, true);
        window.removeEventListener('paste', KeyManager.Instance.paste as any);
        document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
    }

    constructor(props: Readonly<{}>) {
        super(props);
        DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
        MainView.Instance = this;
        DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);

        // causes errors to be generated when modifying an observable outside of an action
        configure({ enforceActions: 'observed' });

        if (window.location.pathname !== '/home') {
            const pathname = window.location.pathname.substr(1).split('/');
            if (pathname.length > 1 && pathname[0] === 'doc') {
                DocServer.GetRefField(pathname[1]).then(
                    action(field => {
                        if (field instanceof Doc && field._type_collection !== CollectionViewType.Docking) {
                            Doc.GuestTarget = field;
                        }
                    })
                );
            }
        }

        library.add(
            ...[
                fa.faExclamationCircle,
                fa.faEdit,
                fa.faArrowDownShortWide,
                fa.faTrash,
                fa.faTrashAlt,
                fa.faShare,
                fa.faTaxi,
                fa.faDownload,
                fa.faExpandArrowsAlt,
                fa.faAmbulance,
                fa.faLayerGroup,
                fa.faExternalLinkAlt,
                fa.faCalendar,
                fa.faSquare,
                far.faSquare as any,
                fa.faConciergeBell,
                fa.faWindowRestore,
                fa.faFolder,
                fa.faFolderOpen,
                fa.faMapPin,
                fa.faMapMarker,
                fa.faFingerprint,
                fa.faCrosshairs,
                fa.faDesktop,
                fa.faUnlock,
                fa.faLock,
                fa.faLaptopCode,
                fa.faMale,
                fa.faCopy,
                fa.faHome,
                fa.faHandPointLeft,
                fa.faHandPointRight,
                fa.faCompass,
                fa.faSnowflake,
                fa.faStar,
                fa.faMicrophone,
                fa.faKeyboard,
                fa.faQuestion,
                fa.faTasks,
                fa.faPalette,
                fa.faAngleLeft,
                fa.faAngleRight,
                fa.faBell,
                fa.faCamera,
                fa.faExpand,
                fa.faCaretDown,
                fa.faCaretLeft,
                fa.faCaretRight,
                fa.faCaretSquareDown,
                fa.faCaretSquareRight,
                fa.faArrowsAltH,
                fa.faPlus,
                fa.faMinus,
                fa.faTerminal,
                fa.faToggleOn,
                fa.faFile,
                fa.faLocationArrow,
                fa.faSearch,
                fa.faFileDownload,
                fa.faFileUpload,
                fa.faStop,
                fa.faCalculator,
                fa.faWindowMaximize,
                fa.faIdCard,
                fa.faAddressCard,
                fa.faQuestionCircle,
                fa.faArrowLeft,
                fa.faArrowRight,
                fa.faArrowDown,
                fa.faArrowUp,
                fa.faBolt,
                fa.faBullseye,
                fa.faTurnUp,
                fa.faTurnDown,
                fa.faCaretUp,
                fa.faCat,
                fa.faCheck,
                fa.faChevronRight,
                fa.faChevronLeft,
                fa.faChevronDown,
                fa.faChevronUp,
                fa.faClone,
                fa.faCloudUploadAlt,
                fa.faCommentAlt,
                fa.faCommentDots,
                fa.faCompressArrowsAlt,
                fa.faCut,
                fa.faEllipsisV,
                fa.faEraser,
                fa.faExclamation,
                fa.faFileAlt,
                fa.faFileAudio,
                fa.faFileVideo,
                fa.faFilePdf,
                fa.faFilm,
                fa.faFilter,
                fa.faFont,
                fa.faGlobeAmericas,
                fa.faGlobeAsia,
                fa.faHighlighter,
                fa.faLongArrowAltRight,
                fa.faMousePointer,
                fa.faMusic,
                fa.faObjectGroup,
                fa.faArrowsLeftRight,
                fa.faPause,
                fa.faPen,
                fa.faPenNib,
                fa.faPhone,
                fa.faPlay,
                fa.faPortrait,
                fa.faRedoAlt,
                fa.faStamp,
                fa.faStickyNote,
                fa.faArrowsAltV,
                fa.faTimesCircle,
                fa.faThumbtack,
                fa.faTree,
                fa.faTv,
                fa.faUndoAlt,
                fa.faVideoSlash,
                fa.faVideo,
                fa.faAsterisk,
                fa.faBrain,
                fa.faImage,
                fa.faPaintBrush,
                fa.faTimes,
                fa.faFlag,
                fa.faEye,
                fa.faArrowsAlt,
                fa.faQuoteLeft,
                fa.faSortAmountDown,
                fa.faAlignLeft,
                fa.faAlignCenter,
                fa.faAlignRight,
                fa.faHeading,
                fa.faRulerCombined,
                fa.faFillDrip,
                fa.faLink,
                fa.faUnlink,
                fa.faBold,
                fa.faItalic,
                fa.faClipboard,
                fa.faUnderline,
                fa.faStrikethrough,
                fa.faSuperscript,
                fa.faSubscript,
                fa.faIndent,
                fa.faEyeDropper,
                fa.faPaintRoller,
                fa.faBars,
                fa.faBrush,
                fa.faShapes,
                fa.faEllipsisH,
                fa.faHandPaper,
                fa.faMap,
                fa.faUser,
                faHireAHelper as any,
                fa.faTrashRestore,
                fa.faUsers,
                fa.faWrench,
                fa.faCog,
                fa.faMap,
                fa.faBellSlash,
                fa.faExpandAlt,
                fa.faArchive,
                fa.faBezierCurve,
                fa.faCircle,
                far.faCircle as any,
                fa.faLongArrowAltRight,
                fa.faPenFancy,
                fa.faAngleDoubleRight,
                fa.faAngleDoubleDown,
                fa.faAngleDoubleLeft,
                fa.faAngleDoubleUp,
                faBuffer as any,
                fa.faExpand,
                fa.faUndo,
                fa.faSlidersH,
                fa.faAngleUp,
                fa.faAngleDown,
                fa.faPlayCircle,
                fa.faClock,
                fa.faRocket,
                fa.faExchangeAlt,
                fa.faHashtag,
                fa.faAlignJustify,
                fa.faCheckSquare,
                fa.faListUl,
                fa.faWindowMinimize,
                fa.faWindowRestore,
                fa.faTextWidth,
                fa.faTextHeight,
                fa.faClosedCaptioning,
                fa.faInfoCircle,
                fa.faTag,
                fa.faSyncAlt,
                fa.faPhotoVideo,
                fa.faArrowAltCircleDown,
                fa.faArrowAltCircleUp,
                fa.faArrowAltCircleLeft,
                fa.faArrowAltCircleRight,
                fa.faStopCircle,
                fa.faCheckCircle,
                fa.faGripVertical,
                fa.faSortUp,
                fa.faSortDown,
                fa.faTable,
                fa.faTableColumns,
                fa.faTh,
                fa.faThList,
                fa.faProjectDiagram,
                fa.faSignature,
                fa.faColumns,
                fa.faChevronCircleUp,
                fa.faUpload,
                fa.faBorderAll,
                fa.faBraille,
                fa.faChalkboard,
                fa.faPencilAlt,
                fa.faEyeSlash,
                fa.faSmile,
                fa.faIndent,
                fa.faOutdent,
                fa.faChartBar,
                fa.faBan,
                fa.faPhoneSlash,
                fa.faGripLines,
                fa.faSave,
                fa.faBook,
                fa.faBookmark,
                fa.faList,
                fa.faListOl,
                fa.faFolderPlus,
                fa.faLightbulb,
                fa.faBookOpen,
                fa.faMapMarkerAlt,
                fa.faSearchPlus,
                fa.faVolumeUp,
                fa.faVolumeDown,
                fa.faSquareRootAlt,
                fa.faVolumeMute,
                fa.faUserCircle,
                fa.faHeart,
                fa.faHeartBroken,
                fa.faHighlighter,
                fa.faRemoveFormat,
                fa.faHandPointUp,
                fa.faXRay,
                fa.faZ,
                fa.faArrowsUpToLine,
                fa.faArrowsDownToLine,
            ]
        );
    }

    private longPressTimer: NodeJS.Timeout | undefined;
    globalPointerClick = action((e: any) => {
        this.longPressTimer && clearTimeout(this.longPressTimer);
        DocumentView.LongPress = false;
    });
    globalPointerMove = action((e: PointerEvent) => {
        if (e.movementX > 3 || e.movementY > 3) this.longPressTimer && clearTimeout(this.longPressTimer);
    });
    globalPointerDown = action((e: PointerEvent) => {
        DocumentView.LongPress = false;
        this.longPressTimer = setTimeout(
            action(() => (DocumentView.LongPress = true)),
            1000
        );
        DocumentManager.removeOverlayViews();
        Doc.linkFollowUnhighlight();
        AudioBox.Enabled = true;
        const targets = document.elementsFromPoint(e.x, e.y);
        if (targets.length) {
            let targClass = targets[0].className.toString();
            for (let i = 0; i < targets.length - 1; i++) {
                if (typeof targets[i].className === 'object') targClass = targets[i + 1].className.toString();
                else break;
            }
            !targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu();
            !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu();
        }
    });

    initEventListeners = () => {
        window.addEventListener('beforeunload', DocServer.UPDATE_SERVER_CACHE);
        window.addEventListener('drop', e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
        window.addEventListener('dragover', e => e.preventDefault(), false);
        document.addEventListener('pointerdown', this.globalPointerDown, true);
        document.addEventListener('pointermove', this.globalPointerMove, true);
        document.addEventListener('pointerup', this.globalPointerClick, true);
        document.addEventListener(
            'click',
            (e: MouseEvent) => {
                if (!e.cancelBubble) {
                    const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join();
                    if (pathstr?.includes('libraryFlyout')) {
                        SelectionManager.DeselectAll();
                    }
                }
            },
            false
        );
        document.oncontextmenu = () => false;
    };

    @action
    createNewPresentation = () => {
        const pres = Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
        CollectionDockingView.AddSplit(pres, OpenWhereMod.right);
        Doc.MyTrails && Doc.AddDocToList(Doc.MyTrails, 'data', pres); // Doc.MyTrails should be created in createDashboard
        Doc.ActivePresentation = pres;
    };

    @action
    openPresentation = (pres: Doc) => {
        if (pres.type === DocumentType.PRES) {
            CollectionDockingView.AddSplit(pres, OpenWhereMod.right);
            Doc.MyTrails && (Doc.ActivePresentation = pres);
            Doc.AddDocToList(Doc.MyTrails, 'data', pres);
            this.closeFlyout();
        }
    };

    @action
    createNewFolder = async () => {
        const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
        Doc.AddDocToList(Doc.MyFilesystem, 'data', folder);
    };

    waitForDoubleClick = () => (DocumentView.ExploreMode ? 'never' : undefined);
    headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
    mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
    addHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
    removeHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
    @computed get headerBarDocView() {
        return (
            <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
                <DocumentView
                    key="headerBarDoc"
                    Document={this.headerBarDoc}
                    DataDoc={undefined}
                    addDocTab={DocumentViewInternal.addDocTabFunc}
                    pinToPres={emptyFunction}
                    docViewPath={returnEmptyDoclist}
                    styleProvider={DefaultStyleProvider}
                    rootSelected={returnTrue}
                    addDocument={this.addHeaderDoc}
                    removeDocument={this.removeHeaderDoc}
                    fitContentsToBox={returnTrue}
                    isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
                    isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
                    ScreenToLocalTransform={this.headerBarScreenXf}
                    childHideResizeHandles={returnTrue}
                    childDragAction="move"
                    dontRegisterView={true}
                    hideResizeHandles={true}
                    PanelWidth={this.headerBarDocWidth}
                    PanelHeight={this.headerBarDocHeight}
                    renderDepth={0}
                    focus={emptyFunction}
                    whenChildContentsActiveChanged={emptyFunction}
                    bringToFront={emptyFunction}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                />
            </div>
        );
    }
    @computed get mainDocView() {
        return (
            <>
                {this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView}
                <DocumentView
                    key="main"
                    Document={this.mainContainer!}
                    DataDoc={undefined}
                    addDocument={undefined}
                    addDocTab={DocumentViewInternal.addDocTabFunc}
                    pinToPres={emptyFunction}
                    docViewPath={returnEmptyDoclist}
                    styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
                    rootSelected={returnTrue}
                    isContentActive={returnTrue}
                    removeDocument={undefined}
                    ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
                    PanelWidth={this.mainDocViewWidth}
                    PanelHeight={this.mainDocViewHeight}
                    focus={emptyFunction}
                    whenChildContentsActiveChanged={emptyFunction}
                    bringToFront={emptyFunction}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                    suppressSetHeight={true}
                    renderDepth={this._hideUI ? 0 : -1}
                />
            </>
        );
    }

    @computed get dockingContent() {
        return (
            <GestureOverlay isActive={LightboxView.LightboxDoc ? false : true}>
                <div
                    key="docking"
                    className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`}
                    onDrop={e => {
                        e.stopPropagation();
                        e.preventDefault();
                    }}
                    style={{
                        width: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
                        minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
                        transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined,
                    }}>
                    {!this.mainContainer ? null : this.mainDocView}
                </div>
            </GestureOverlay>
        );
    }

    @action
    onPropertiesPointerDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            action(e => ((SettingsManager.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
            action(() => SettingsManager.propertiesWidth < 5 && (SettingsManager.propertiesWidth = 0)),
            action(() => (SettingsManager.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
            false
        );
    };

    @action
    onFlyoutPointerDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            action(e => ((this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false)),
            () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
            this.closeFlyout
        );
    };

    sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
    mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
    static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => {
        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 OpenWhere.lightbox:   return LightboxView.AddDocTab(doc, location);
            case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, undefined, "dontSelectOnActivate"); // bcz: hack!  mark the toggle so that it won't be selected on activation- this is needed so that the backlinks menu can toggle views of targets on and off without selecting them
            case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, undefined, undefined, keyValue);
        }
    };

    @computed get flyout() {
        return !this._leftMenuFlyoutWidth ? (
            <div key="flyout" className={`mainView-libraryFlyout-out`}>
                {this.docButtons}
            </div>
        ) : (
            <div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }}>
                <div className="mainView-contentArea">
                    <DocumentView
                        Document={this._sidebarContent.proto || this._sidebarContent}
                        DataDoc={undefined}
                        addDocument={undefined}
                        addDocTab={DocumentViewInternal.addDocTabFunc}
                        pinToPres={emptyFunction}
                        docViewPath={returnEmptyDoclist}
                        styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
                        rootSelected={returnTrue}
                        removeDocument={returnFalse}
                        ScreenToLocalTransform={this.mainContainerXf}
                        PanelWidth={this.leftMenuFlyoutWidth}
                        PanelHeight={this.leftMenuFlyoutHeight}
                        renderDepth={0}
                        isContentActive={returnTrue}
                        scriptContext={CollectionDockingView.Instance?.props.Document}
                        focus={emptyFunction}
                        whenChildContentsActiveChanged={emptyFunction}
                        bringToFront={emptyFunction}
                        childFilters={returnEmptyFilter}
                        childFiltersByRanges={returnEmptyFilter}
                        searchFilterDocs={returnEmptyDoclist}
                    />
                </div>
                {this.docButtons}
            </div>
        );
    }

    @computed get leftMenuPanel() {
        return (
            <div key="menu" className="mainView-leftMenuPanel" style={{ background: SettingsManager.userBackgroundColor, display: LightboxView.LightboxDoc ? 'none' : undefined }}>
                <DocumentView
                    Document={Doc.MyLeftSidebarMenu}
                    DataDoc={undefined}
                    addDocument={undefined}
                    addDocTab={DocumentViewInternal.addDocTabFunc}
                    pinToPres={emptyFunction}
                    rootSelected={returnTrue}
                    removeDocument={returnFalse}
                    ScreenToLocalTransform={this.sidebarScreenToLocal}
                    PanelWidth={this.leftMenuWidth}
                    PanelHeight={this.leftMenuHeight}
                    renderDepth={0}
                    docViewPath={returnEmptyDoclist}
                    focus={emptyFunction}
                    styleProvider={DefaultStyleProvider}
                    isContentActive={returnTrue}
                    whenChildContentsActiveChanged={emptyFunction}
                    bringToFront={emptyFunction}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                    scriptContext={this}
                />
            </div>
        );
    }

    @action
    selectMenu = (button: Doc) => {
        const title = StrCast(Doc.GetProto(button).title);
        const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
        this.closeFlyout();
        if (willOpen) {
            switch ((this._panelContent = title)) {
                case 'Settings':
                    SettingsManager.Instance.open();
                    break;
                case 'Help':
                    break;
                default:
                    this.expandFlyout(button);
            }
        }
        return true;
    };

    @computed get mainInnerContent() {
        const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth();
        const width = this.propertiesWidth() + leftMenuFlyoutWidth;
        return (
            <>
                {this._hideUI ? null : this.leftMenuPanel}
                <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
                    {this.flyout}
                    <div
                        className="mainView-libraryHandle"
                        style={{ background: SettingsManager.userBackgroundColor, left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }}
                        onPointerDown={this.onFlyoutPointerDown}>
                        <FontAwesomeIcon icon="chevron-left" color={SettingsManager.userColor} style={{ opacity: '50%' }} size="sm" />
                    </div>
                    <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
                        {this.dockingContent}

                        {this._hideUI ? null : (
                            <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ background: SettingsManager.userVariantColor, right: this.propertiesWidth() - 1 }}>
                                <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={SettingsManager.userColor} size="sm" />
                            </div>
                        )}
                        <div className="properties-container" style={{ width: this.propertiesWidth(), color: SettingsManager.userColor }}>
                            {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
                        </div>
                    </div>
                </div>
            </>
        );
    }

    @computed get mainDashboardArea() {
        return !this.userDoc ? null : (
            <div
                className="mainView-dashboardArea"
                ref={r => {
                    r &&
                        new _global.ResizeObserver(
                            action(() => {
                                this._dashUIWidth = r.getBoundingClientRect().width;
                                this._dashUIHeight = r.getBoundingClientRect().height;
                            })
                        ).observe(r);
                }}
                style={{
                    color: this.colorScheme === ColorScheme.Dark ? 'rgb(205,205,205)' : 'black',
                    height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`,
                    width: '100%',
                }}>
                {this.mainInnerContent}
            </div>
        );
    }

    expandFlyout = action((button: Doc) => {
        // bcz: What's going on here!? --- may be fixed now, so commenting out ...
        // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab.  All of the div below the CollectionFreeFormView's main div
        // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
        // interactively adjusting the flyout fixes the problem.  So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
        this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
        //setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));

        this._sidebarContent.proto = DocCast(button.target);
        DocumentView.LastPressedSidebarBtn = button;
    });

    closeFlyout = action(() => {
        DocumentView.LastPressedSidebarBtn = undefined;
        this._panelContent = 'none';
        this._sidebarContent.proto = undefined;
        this._leftMenuFlyoutWidth = 0;
    });

    remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
    moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
    addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);

    buttonBarXf = () => {
        if (!this._docBtnRef.current) return Transform.Identity();
        const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
        return new Transform(-translateX, -translateY, 1 / scale);
    };

    @computed get docButtons() {
        return !Doc.MyDockedBtns ? null : (
            <div className="mainView-docButtons" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }} ref={this._docBtnRef}>
                <CollectionLinearView
                    Document={Doc.MyDockedBtns}
                    DataDoc={undefined}
                    fieldKey="data"
                    dropAction="embed"
                    setHeight={returnFalse}
                    styleProvider={DefaultStyleProvider}
                    rootSelected={returnTrue}
                    bringToFront={emptyFunction}
                    select={emptyFunction}
                    isAnyChildContentActive={returnFalse}
                    isContentActive={emptyFunction}
                    isSelected={returnFalse}
                    docViewPath={returnEmptyDoclist}
                    moveDocument={this.moveButtonDoc}
                    addDocument={this.addButtonDoc}
                    addDocTab={DocumentViewInternal.addDocTabFunc}
                    pinToPres={emptyFunction}
                    removeDocument={this.remButtonDoc}
                    ScreenToLocalTransform={this.buttonBarXf}
                    PanelWidth={this.leftMenuFlyoutWidth}
                    PanelHeight={this.leftMenuFlyoutHeight}
                    renderDepth={0}
                    focus={emptyFunction}
                    whenChildContentsActiveChanged={emptyFunction}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                />
                {['watching', 'recording'].includes(StrCast(this.userDoc?.presentationMode)) ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>}
            </div>
        );
    }
    @computed get snapLines() {
        return !SelectionManager.Views().some(dv => dv.rootDoc.freeform_snapLines) ? null : (
            <div className="mainView-snapLines">
                <svg style={{ width: '100%', height: '100%' }}>
                    {SnappingManager.horizSnapLines().map((l, i) => (
                        <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
                    ))}
                    {SnappingManager.vertSnapLines().map((l, i) => (
                        <line key={i} y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} />
                    ))}
                </svg>
            </div>
        );
    }

    @computed get inkResources() {
        return (
            <svg width={0} height={0}>
                <defs>
                    <filter id="inkSelectionHalo">
                        <feColorMatrix
                            type="matrix"
                            result="color"
                            values="1 0 0 0 0
                0 0 0 0 0
                0 0 0 0 0
                0 0 0 1 0"></feColorMatrix>
                        <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur>
                        <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset>
                        <feMerge>
                            <feMergeNode in="bg"></feMergeNode>
                            <feMergeNode in="offset"></feMergeNode>
                            <feMergeNode in="SourceGraphic"></feMergeNode>
                        </feMerge>
                    </filter>
                </defs>
            </svg>
        );
    }

    @computed get linkDocPreview() {
        return LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null;
    }
    @observable mapBoxHackBool = false;
    @computed get mapBoxHack() {
        return this.mapBoxHackBool ? null : (
            <MapBox
                ref={action((r: any) => r && (this.mapBoxHackBool = true))}
                fieldKey="data"
                select={returnFalse}
                isSelected={returnFalse}
                Document={this.headerBarDoc}
                DataDoc={undefined}
                addDocTab={returnFalse}
                pinToPres={emptyFunction}
                docViewPath={returnEmptyDoclist}
                styleProvider={DefaultStyleProvider}
                rootSelected={returnTrue}
                addDocument={returnFalse}
                removeDocument={returnFalse}
                fitContentsToBox={returnTrue}
                isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
                isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
                ScreenToLocalTransform={Transform.Identity}
                childHideResizeHandles={returnTrue}
                childDragAction="move"
                dontRegisterView={true}
                PanelWidth={this.headerBarDocWidth}
                PanelHeight={this.headerBarDocHeight}
                renderDepth={0}
                focus={emptyFunction}
                whenChildContentsActiveChanged={emptyFunction}
                bringToFront={emptyFunction}
                childFilters={returnEmptyFilter}
                childFiltersByRanges={returnEmptyFilter}
                searchFilterDocs={returnEmptyDoclist}
            />
        );
    }

    render() {
        return (
            <div
                className={`mainView-container ${this.colorScheme}`}
                style={{
                    color: SettingsManager.userColor,
                    background: SettingsManager.userBackgroundColor,
                }}
                onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)}
                ref={r => {
                    r &&
                        new _global.ResizeObserver(
                            action(() => {
                                this._windowWidth = r.getBoundingClientRect().width;
                                this._windowHeight = r.getBoundingClientRect().height;
                            })
                        ).observe(r);
                }}>
                {this.inkResources}
                <DictationOverlay />
                <SharingManager />
                <ServerStats />
                <RTFMarkup />
                <SettingsManager />
                <ReportManager />
                <CaptureManager />
                <GroupManager />
                <GoogleAuthenticationManager />
                <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfSidebarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
                <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
                {this._hideUI ? null : <TopBar />}
                {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
                {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
                {this.linkDocPreview}

                {((page: string) => {
                    // prettier-ignore
                    switch (page) {
                        default:
                        case 'dashboard': return (<>
                                        <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
                                            <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
                                        </div>
                                        {this.mainDashboardArea}
                                    </> );
                        case 'home': return <DashboardView />;
                    }
                })(Doc.ActivePage)}

                <PreviewCursor />
                <TaskCompletionBox />
                <ContextMenu />
                <RadialMenu />
                <AnchorMenu />
                <MapAnchorMenu />
                <DashFieldViewMenu />
                <MarqueeOptionsMenu />
                <TimelineMenu />
                <RichTextMenu />
                <InkTranscription />
                {this.snapLines}
                <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
                <OverlayView />
                {this.mapBoxHack}
                <GPTPopup key="gptpopup" />
                <GenerativeFill imageEditorOpen={ImageBox.imageEditorOpen} imageEditorSource={ImageBox.imageEditorSource} imageRootDoc={ImageBox.imageRootDoc} addDoc={ImageBox.addDoc} />
                {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
            </div>
        );
    }
}

ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) {
    MainView.Instance.selectMenu(doc);
});
ScriptingGlobals.add(function createNewPresentation() {
    return MainView.Instance.createNewPresentation();
}, 'creates a new presentation when called');
ScriptingGlobals.add(function openPresentation(pres: Doc) {
    return MainView.Instance.openPresentation(pres);
}, 'creates a new presentation when called');
ScriptingGlobals.add(function createNewFolder() {
    return MainView.Instance.createNewFolder();
}, 'creates a new folder in myFiles when called');
