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 { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import ResizeObserver from 'resize-observer-polyfill';
import '@dash/components/src/global/globalCssVariables.scss';
import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
import { emptyFunction } from '../../Utils';
import { Doc, DocListCast, GetDocFromUrl, Opt, returnEmptyDoclist } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { DocCast, StrCast, toList } from '../../fields/Types';
import { DocServer } from '../DocServer';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { Docs } from '../documents/Documents';
import { CalendarManager } from '../util/CalendarManager';
import { CaptureManager } from '../util/CaptureManager';
import { CurrentUserUtils, ToTagName } from '../util/CurrentUserUtils';
import { DocumentManager } from '../util/DocumentManager';
import { DragManager } from '../util/DragManager';
import { dropActionType } from '../util/DropActionTypes';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
import { UPDATE_SERVER_CACHE } from '../util/LinkManager';
import { RTFMarkup } from '../util/RTFMarkup';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { ServerStats } from '../util/ServerStats';
import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
import { ReportManager } from '../util/reportManager/ReportManager';
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 { InkTranscription } from './InkTranscription';
import { LightboxView } from './LightboxView';
import './MainView.scss';
import { ObservableReactComponent } from './ObservableReactComponent';
import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { DashboardStyleProvider, DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionMenu } from './collections/CollectionMenu';
import { TabDocView } from './collections/TabDocView';
import './collections/TreeView.scss';
import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { ImageLabelHandler } from './collections/collectionFreeForm/ImageLabelHandler';
import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
import { CollectionLinearView } from './collections/collectionLinear';
import { LinkMenu } from './linking/LinkMenu';
import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu';
import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
import { DocButtonState } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
import { ImageEditorData as ImageEditor } from './nodes/ImageBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
import { TaskCompletionBox } from './nodes/TaskCompletedBox';
import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
import ImageEditorBox from './nodes/imageEditor/ImageEditor';
import { PresBox } from './nodes/trails';
import { AnchorMenu } from './pdf/AnchorMenu';
import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
import { SmartDrawHandler } from './smartdraw/SmartDrawHandler';
import { TopBar } from './topbar/TopBar';
import { OverlayView } from './OverlayView';

// eslint-disable-next-line @typescript-eslint/no-require-imports
const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore

@observer
export class MainView extends ObservableReactComponent<object> {
    // eslint-disable-next-line no-use-before-define
    public static Instance: MainView;
    public static Live: boolean = false;
    private _docBtnRef = React.createRef<HTMLDivElement>();

    @observable private _keepContextMenuOpen: boolean = false;
    @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: Doc = Doc.MyLeftSidebarPanel;
    @observable private _leftMenuFlyoutWidth: number = 0;
    @computed get _hideUI() {
        return SnappingManager.HideUI || (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 || DocumentView.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();
    }
    @observable mainDoc: Opt<Doc> = undefined;
    @computed private get mainContainer() {
        if (window.location.pathname.startsWith('/doc/') && ClientUtils.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));
    }
    @observable public headerBarHeight: number = 0;
    headerBarHeightFunc = () => this.headerBarHeight;

    @action
    toggleTopBar = () => {
        if (this.headerBarHeight > 0) {
            this.headerBarHeight = 0;
        } else {
            this.headerBarHeight = 60;
        }
    };
    headerBarDocWidth = () => this.mainDocViewWidth();
    headerBarDocHeight = () => (this._hideUI ? 0 : (this.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, SnappingManager.PropertiesWidth || 0));
    propertiesHeight = () => this._dashUIHeight;
    mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
    mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();

    componentDidMount() {
        // Utils.TraceConsoleLog();
        reaction(
            // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection
            () => DocumentView.Selected().slice(),
            views => views.length > 1 && document.activeElement instanceof HTMLElement && document.activeElement?.blur()
        );
        reaction(
            () => Doc.MyDockedBtns.linearView_IsOpen,
            open => SnappingManager.SetPrintToConsole(!!open)
        );
        const scriptTag = document.createElement('script');
        scriptTag.setAttribute('type', 'text/javascript');
        scriptTag.setAttribute('src', 'https://www.bing.com/api/maps/mapcontrol?callback=makeMap');
        scriptTag.async = true;
        scriptTag.defer = true;
        document.body.appendChild(scriptTag);
        document.getElementById('root')?.addEventListener('scroll', () =>
            (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);
        document.addEventListener('dash', (e: Event) => {
            // event used by chrome plugin to tell Dash which document to focus on
            const id = GetDocFromUrl((e as Event & { detail: string }).detail);
            DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentView.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: object) {
        super(props);
        makeObservable(this);
        DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
        MainView.Instance = this;
        DashboardView._urlState = HistoryUtil.parseUrl(window.location) ?? { type: 'doc', docId: '' };

        // 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.faMinimize,
                fa.faArrowsRotate,
                fa.faFloppyDisk,
                fa.faRepeat,
                fa.faArrowsUpDown,
                fa.faArrowsLeftRight,
                fa.faWindowMaximize,
                fa.faGift,
                fa.faLockOpen,
                fa.faSort,
                fa.faArrowUpZA,
                fa.faArrowDownAZ,
                fa.faExclamationCircle,
                fa.faEdit,
                fa.faArrowDownShortWide,
                fa.faTrash,
                fa.faTrashAlt,
                fa.faShare,
                fa.faTaxi,
                fa.faDownload,
                fa.faPallet,
                fa.faExpandArrowsAlt,
                fa.faAmbulance,
                fa.faLayerGroup,
                fa.faExternalLinkAlt,
                fa.faCalendar,
                fa.faSquare,
                far.faSquare,
                fa.faConciergeBell,
                fa.faWindowRestore,
                fa.faFolder,
                fa.faFolderOpen,
                fa.faFolderPlus,
                fa.faFolderClosed,
                fa.faBook,
                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.faSplotch,
                fa.faMicrophone,
                fa.faCircleHalfStroke,
                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.faFileExport,
                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.faDeleteLeft,
                fa.faXmarksLines,
                fa.faCircleXmark,
                fa.faXmark,
                fa.faExclamation,
                fa.faFileAlt,
                fa.faFileArrowDown,
                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.faUserPen,
                fa.faPenNib,
                fa.faPhone,
                fa.faPlay,
                fa.faPortrait,
                fa.faRedoAlt,
                fa.faStamp,
                fa.faStickyNote,
                fa.faArrowsAltV,
                fa.faTimesCircle,
                fa.faThumbtack,
                fa.faScissors,
                fa.faTree,
                fa.faTv,
                fa.faUndoAlt,
                fa.faVideoSlash,
                fa.faVideo,
                fa.faAsterisk,
                fa.faBrain,
                fa.faImage,
                fa.faPaintBrush,
                fa.faTimes,
                fa.faFlag,
                fa.faScroll,
                fa.faEye,
                fa.faArrowsAlt,
                fa.faQuoteLeft,
                fa.faSortAmountDown,
                fa.faAlignLeft,
                fa.faAlignCenter,
                fa.faAlignRight,
                fa.faHeading,
                fa.faRulerCombined,
                fa.faFill,
                fa.faFillDrip,
                fa.faLink,
                fa.faUnlink,
                fa.faBold,
                fa.faItalic,
                fa.faClipboard,
                fa.faClipboardCheck,
                fa.faUnderline,
                fa.faStrikethrough,
                fa.faSuperscript,
                fa.faSubscript,
                fa.faIndent,
                fa.faEyeDropper,
                fa.faPaintRoller,
                fa.faBars,
                fa.faBarsStaggered,
                fa.faBrush,
                fa.faShapes,
                fa.faEllipsisH,
                fa.faHandPaper,
                fa.faMap,
                fa.faUser,
                faHireAHelper,
                fa.faTrashRestore,
                fa.faUsers,
                fa.faWrench,
                fa.faCog,
                fa.faMap,
                fa.faBellSlash,
                fa.faExpandAlt,
                fa.faArchive,
                fa.faBezierCurve,
                fa.faCircle,
                far.faCircle,
                fa.faLongArrowAltRight,
                fa.faPenFancy,
                fa.faAngleDoubleRight,
                fa.faAngleDoubleDown,
                fa.faAngleDoubleLeft,
                fa.faAngleDoubleUp,
                faBuffer,
                fa.faExpand,
                fa.faUndo,
                fa.faSlidersH,
                fa.faAngleUp,
                fa.faAngleDown,
                fa.faPlayCircle,
                fa.faClock,
                fa.faRoute,
                fa.faRocket,
                fa.faExchangeAlt,
                fa.faHashtag,
                fa.faAlignJustify,
                fa.faCheckSquare,
                fa.faSquarePlus,
                fa.faReply,
                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.faTableCells,
                fa.faTableColumns,
                fa.faTh,
                fa.faThList,
                fa.faProjectDiagram,
                fa.faSignature,
                fa.faColumns,
                fa.faChevronCircleUp,
                fa.faUpload,
                fa.faBorderAll,
                fa.faBraille,
                fa.faPersonChalkboard,
                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.faLightbulb,
                fa.faBookOpen,
                fa.faMapMarkerAlt,
                fa.faSearchPlus,
                fa.faSolarPanel,
                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,
                fa.faPalette,
                fa.faHourglassHalf,
                fa.faRobot,
                fa.faSatellite,
                fa.faStar,
                fa.faFilePen,
                fa.faCloud,
                fa.faBolt,
                fa.faLightbulb,
                fa.faX,
            ]
        );
    }

    private longPressTimer: NodeJS.Timeout | undefined;
    globalPointerClick = action(() => {
        this.longPressTimer && clearTimeout(this.longPressTimer);
        SnappingManager.SetLongPress(false);
    });
    globalPointerMove = action((e: PointerEvent) => {
        if (e.movementX > 3 || e.movementY > 3) this.longPressTimer && clearTimeout(this.longPressTimer);
    });
    globalPointerDown = action((e: PointerEvent) => {
        SnappingManager.SetLongPress(false);
        this.longPressTimer = setTimeout(
            action(() => {
                SnappingManager.SetLongPress(true);
            }),
            1000
        );
        DocumentManager.removeOverlayViews();
        Doc.linkFollowUnhighlight();
        const targets = document.elementsFromPoint(e.x, e.y);
        const targetClasses = targets.map(target => target.className.toString());
        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();
            !targetClasses.includes('marqueeView') && !targetClasses.includes('smart-draw-handler') && SmartDrawHandler.Instance.hideSmartDrawHandler();
            !targetClasses.includes('smart-draw-handler') && SmartDrawHandler.Instance.hideRegenerate();
            !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu();
        }
    });

    initEventListeners = () => {
        window.addEventListener('beforeunload', 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.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, undefined, PresBox.PanelName);
            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 = () => (SnappingManager.ExploreMode ? 'never' : undefined);
    headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
    mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
    addHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
    removeHeaderDoc = (docs: Doc | Doc[]) => toList(docs).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}
                    addDocTab={DocumentViewInternal.addDocTabFunc}
                    pinToPres={emptyFunction}
                    containerViewPath={returnEmptyDocViewList}
                    styleProvider={DefaultStyleProvider}
                    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
                    childDragAction={dropActionType.move}
                    dontRegisterView
                    hideResizeHandles
                    PanelWidth={this.headerBarDocWidth}
                    PanelHeight={this.headerBarDocHeight}
                    renderDepth={0}
                    focus={emptyFunction}
                    whenChildContentsActiveChanged={emptyFunction}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                />
            </div>
        );
    }
    @computed get mainDocView() {
        const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView;
        return (
            <>
                {headerBar}
                <DocumentView
                    key="main"
                    Document={this.mainContainer!}
                    addDocument={undefined}
                    addDocTab={DocumentViewInternal.addDocTabFunc}
                    pinToPres={emptyFunction}
                    containerViewPath={returnEmptyDocViewList}
                    styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
                    isContentActive={returnTrue}
                    removeDocument={undefined}
                    ScreenToLocalTransform={this._hideUI ? this.mainScreenToLocalXf : Transform.Identity}
                    PanelWidth={this.mainDocViewWidth}
                    PanelHeight={this.mainDocViewHeight}
                    focus={emptyFunction}
                    whenChildContentsActiveChanged={emptyFunction}
                    childFilters={returnEmptyFilter}
                    childFiltersByRanges={returnEmptyFilter}
                    searchFilterDocs={returnEmptyDoclist}
                    suppressSetHeight
                    renderDepth={-1}
                />
            </>
        );
    }

    @computed get dockingContent() {
        return (
            <GestureOverlay isActive={!DocumentView.LightboxDoc()}>
                <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: DocumentView.LightboxDoc() ? 'scale(0.0001)' : undefined,
                    }}>
                    {!this.mainContainer ? null : this.mainDocView}
                </div>
            </GestureOverlay>
        );
    }

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

    @action
    onFlyoutPointerDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            action(ev => {
                this._leftMenuFlyoutWidth = Math.max(ev.clientX - 58, 0);
                return 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 = (docs: Doc | Doc[], location: OpenWhere): boolean => {
        const doc = toList(docs).lastElement();
        const whereFields = location.split(':');
        const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
        const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
        const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
        if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
        switch (whereFields[0]) {
            case OpenWhere.lightbox:   return LightboxView.Instance.AddDocTab(doc, location);
            case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, undefined, TabDocView.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.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, undefined, panelName);
            case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, undefined, undefined, keyValue);
        } // prettier-ignore
    };

    @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={DocCast(this._sidebarContent.proto, this._sidebarContent)}
                        addDocument={undefined}
                        addDocTab={DocumentViewInternal.addDocTabFunc}
                        pinToPres={DocumentView.PinDoc}
                        containerViewPath={returnEmptyDocViewList}
                        styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
                        removeDocument={returnFalse}
                        ScreenToLocalTransform={this.mainContainerXf}
                        PanelWidth={this.leftMenuFlyoutWidth}
                        PanelHeight={this.leftMenuFlyoutHeight}
                        renderDepth={0}
                        isContentActive={returnTrue}
                        focus={emptyFunction}
                        whenChildContentsActiveChanged={emptyFunction}
                        childFilters={returnEmptyFilter}
                        childFiltersByRanges={returnEmptyFilter}
                        searchFilterDocs={returnEmptyDoclist}
                    />
                </div>
                {this.docButtons}
            </div>
        );
    }

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

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

    /**
     * Allows users to add a filter hotkey to the properties panel. Will also update the multitoggle at the top menu and the
     * icontags tht are displayed on the documents themselves
     * @param hotKey tite of the new hotkey
     */
    addHotKey = (hotKey: string) => {
        const filterIcons = DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter);
        const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons);
        Doc.AddToFilterHotKeys(menuDoc);
    };

    @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.flyout}
                    <div
                        className="mainView-libraryHandle"
                        style={{ background: SnappingManager.userBackgroundColor, left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }}
                        onPointerDown={this.onFlyoutPointerDown}>
                        <FontAwesomeIcon icon="chevron-left" color={SnappingManager.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${this.propertiesWidth() < 10 ? '-minified' : ''}`}
                                key="props"
                                onPointerDown={this.onPropertiesPointerDown}
                                style={{ background: SnappingManager.userVariantColor, right: this.propertiesWidth() - 1 }}>
                                <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={SnappingManager.userColor} size="sm" />
                            </div>
                        )}
                        <div className="properties-container" style={{ width: this.propertiesWidth(), color: SnappingManager.userColor }}>
                            <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
                                <PropertiesView styleProvider={DefaultStyleProvider} addHotKey={this.addHotKey} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
                            </div>
                        </div>
                    </div>
                </div>
            </>
        );
    }

    @computed get mainDashboardArea() {
        return !this.userDoc ? null : (
            <div
                className="mainView-dashboardArea"
                ref={r => {
                    r &&
                        new ResizeObserver(
                            action(() => {
                                this._dashUIWidth = r.getBoundingClientRect().width;
                                this._dashUIHeight = r.getBoundingClientRect().height;
                            })
                        ).observe(r);
                }}
                style={{
                    color: '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);
        SnappingManager.SetLastPressedBtn(button[Id]);
    });

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

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

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

    @computed get docButtons() {
        return !Doc.MyDockedBtns ? null : (
            <div className="mainView-docButtons" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }} ref={this._docBtnRef}>
                <CollectionLinearView
                    Document={Doc.MyDockedBtns}
                    docViewPath={returnEmptyDocViewList}
                    fieldKey="data"
                    dropAction={dropActionType.embed}
                    styleProvider={DefaultStyleProvider}
                    select={emptyFunction}
                    isAnyChildContentActive={returnFalse}
                    isContentActive={emptyFunction}
                    isSelected={returnFalse}
                    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> : null}
            </div>
        );
    }
    @computed get snapLines() {
        const dragged = DragManager.docsBeingDragged.lastElement() ?? DocumentView.SelectedDocs().lastElement();
        const dragPar = dragged ? CollectionFreeFormView.from(DocumentView.getViews(dragged).lastElement()) : undefined;
        return !dragPar?.layoutDoc.freeform_snapLines ? null : (
            <div className="mainView-snapLines">
                <svg style={{ width: '100%', height: '100%' }}>
                    {[
                        ...SnappingManager.HorizSnapLines.map(l => (
                            <line
                                key={'horiz' + l}
                                x1="0"
                                y1={l}
                                x2="2000"
                                y2={l}
                                stroke={
                                    SnappingManager.userVariantColor
                                    /* lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))*/
                                }
                                opacity={0.3}
                                strokeWidth={3}
                                strokeDasharray="2 2"
                            />
                        )),
                        ...SnappingManager.VertSnapLines.map(l => (
                            <line
                                key={'vert' + l}
                                y1={this.topOfMainDocContent.toString()}
                                x1={l}
                                y2="2000"
                                x2={l}
                                stroke={
                                    SnappingManager.userVariantColor
                                    /* lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))*/
                                }
                                opacity={0.3}
                                strokeWidth={3}
                                strokeDasharray="2 2"
                            />
                        )),
                    ]}
                </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"
                        />
                        <feGaussianBlur in="color" stdDeviation="4" result="blur" />
                        <feOffset in="blur" dx="0" dy="0" result="offset" />
                        <feMerge>
                            <feMergeNode in="bg" />
                            <feMergeNode in="offset" />
                            <feMergeNode in="SourceGraphic" />
                        </feMerge>
                    </filter>
                </defs>
            </svg>
        );
    }

    togglePropertiesFlyout = () => {
        if (MainView.Instance.propertiesWidth() > 0) {
            SnappingManager.SetPropertiesWidth(0);
        } else {
            SnappingManager.SetPropertiesWidth(300);
        }
    };

    lightboxMaxBorder = [200, 50];
    render() {
        return (
            <div
                className="mainView-container"
                style={{
                    color: SnappingManager.userColor,
                    background: SnappingManager.userBackgroundColor,
                }}
                onScroll={() =>
                    (ele => {
                        ele.scrollTop = ele.scrollLeft = 0;
                    })(document.getElementById('root')!)
                }
                ref={r => {
                    r &&
                        new ResizeObserver(
                            action(() => {
                                this._windowWidth = r.getBoundingClientRect().width;
                                this._windowHeight = r.getBoundingClientRect().height;
                            })
                        ).observe(r);
                }}>
                {this.inkResources}
                <DictationOverlay />
                <SharingManager />
                <CalendarManager />
                <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 />
                {DocButtonState.Instance.LinkEditorDocView ? (
                    <LinkMenu
                        clearLinkEditor={action(() => {
                            DocButtonState.Instance.LinkEditorDocView = undefined;
                        })}
                        docView={DocButtonState.Instance.LinkEditorDocView}
                    />
                ) : null}
                {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null}
                {((page: string) => {
                    // prettier-ignore
                    switch (page) {
                        case 'home': return <DashboardView />;
                        case 'dashboard':
                        default: return (<>
                                        <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || DocumentView.LightboxDoc() ? 'none' : undefined, zIndex: 2001 }}>
                                            <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} togglePropertiesFlyout={this.togglePropertiesFlyout} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
                                        </div>
                                        {this.mainDashboardArea}
                                    </> );
                    }
                })(Doc.ActivePage)}
                <PreviewCursor />
                <TaskCompletionBox />
                <ContextMenu />
                <DocCreatorMenu />
                <ImageLabelHandler />
                <SmartDrawHandler />
                <AnchorMenu />
                <MapAnchorMenu />
                <DirectionsAnchorMenu />
                <DashFieldViewMenu />
                <MarqueeOptionsMenu />
                <TimelineMenu />
                <RichTextMenu />
                <InkTranscription />
                {this.snapLines}
                <LightboxView key="lightbox" PanelWidth={this._windowWidth} addSplit={CollectionDockingView.AddSplit} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} />
                <SchemaCSVPopUp key="schemacsvpopup" />
                <ImageEditorBox imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
            </div>
        );
    }
}

// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function selectMainMenu(doc: Doc) {
    MainView.Instance.selectMenu(doc);
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function hideUI() {
    SnappingManager.SetHideUI(!SnappingManager.HideUI);
});
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function createNewPresentation() {
    return MainView.Instance.createNewPresentation();
}, 'creates a new presentation when called');
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function openPresentation(pres: Doc) {
    return MainView.Instance.openPresentation(pres);
}, 'creates a new presentation when called');
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function createNewFolder() {
    return MainView.Instance.createNewFolder();
}, 'creates a new folder in myFiles when called');
