import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Height, Width } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField, PdfField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { KeyCodes } from '../../util/KeyCodes';
import { SelectionManager } from '../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { CollectionStackingView } from '../collections/CollectionStackingView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { CreateImage } from '../nodes/WebBoxRenderer';
import { PDFViewer } from '../pdf/PDFViewer';
import { SidebarAnnos } from '../SidebarAnnos';
import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { ImageBox } from './ImageBox';
import './PDFBox.scss';
import { PinProps, PresBox } from './trails';
import React = require('react');

@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(PDFBox, fieldKey);
    }
    public static openSidebarWidth = 250;
    public static sidebarResizerWidth = 5;
    private _searchString: string = '';
    private _initialScrollTarget: Opt<Doc>;
    private _pdfViewer: PDFViewer | undefined;
    private _searchRef = React.createRef<HTMLInputElement>();
    private _disposers: { [name: string]: IReactionDisposer } = {};
    private _sidebarRef = React.createRef<SidebarAnnos>();

    @observable private _searching: boolean = false;
    @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
    @observable private _pageControls = false;

    @computed get pdfUrl() {
        return Cast(this.dataDoc[this.props.fieldKey], PdfField);
    }
    @computed get pdfThumb() {
        return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
    }

    constructor(props: any) {
        super(props);
        const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
        const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
        if (this.pdfUrl) {
            if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)));
            else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
        }
    }

    replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
        if (oldDiv.childNodes) {
            for (let i = 0; i < oldDiv.childNodes.length; i++) {
                this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
            }
        }

        if (oldDiv.className === 'pdfBox-ui' || oldDiv.className === 'pdfViewerDash-overlay-inking') {
            newDiv.style.display = 'none';
        }
        if (newDiv?.style) newDiv.style.overflow = 'hidden';
        if (oldDiv instanceof HTMLCanvasElement) {
            const canvas = oldDiv;
            const img = document.createElement('img'); // create a Image Element
            img.src = canvas.toDataURL(); //image sourcez
            img.style.width = canvas.style.width;
            img.style.height = canvas.style.height;
            const newCan = newDiv as HTMLCanvasElement;
            const parEle = newCan.parentElement as HTMLElement;
            parEle.removeChild(newCan);
            parEle.appendChild(img);
        }
    };

    crop = (region: Doc | undefined, addCrop?: boolean) => {
        if (!region) return;
        const cropping = Doc.MakeCopy(region, true);
        Doc.GetProto(region).lockedPosition = true;
        Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
        Doc.GetProto(region).followLinkToggle = true;
        this.addDocument(region);

        const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
        const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
        newDiv.style.width = this.layoutDoc[Width]().toString();
        newDiv.style.height = this.layoutDoc[Height]().toString();
        this.replaceCanvases(docViewContent, newDiv);
        const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);

        const anchx = NumCast(cropping.x);
        const anchy = NumCast(cropping.y);
        const anchw = cropping[Width]() * (this.props.NativeDimScaling?.() || 1);
        const anchh = cropping[Height]() * (this.props.NativeDimScaling?.() || 1);
        const viewScale = 1;
        cropping.title = 'crop: ' + this.rootDoc.title;
        cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
        cropping.y = NumCast(this.rootDoc.y);
        cropping._width = anchw;
        cropping._height = anchh;
        cropping.onClick = undefined;
        const croppingProto = Doc.GetProto(cropping);
        croppingProto.annotationOn = undefined;
        croppingProto.isDataDoc = true;
        croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
        croppingProto.type = DocumentType.IMG;
        croppingProto.layout = ImageBox.LayoutString('data');
        croppingProto.data = new ImageField(Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
        croppingProto['data_nativeWidth'] = anchw;
        croppingProto['data_nativeHeight'] = anchh;
        if (addCrop) {
            DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
        }
        this.props.bringToFront(cropping);

        CreateImage(
            '',
            document.styleSheets,
            htmlString,
            anchw,
            anchh,
            (NumCast(region.y) * this.props.PanelWidth()) / NumCast(this.rootDoc[this.fieldKey + '_nativeWidth']),
            (NumCast(region.x) * this.props.PanelWidth()) / NumCast(this.rootDoc[this.fieldKey + '_nativeWidth']),
            4
        )
            .then((data_url: any) => {
                Utils.convertDataUri(data_url, region[Id]).then(returnedfilename =>
                    setTimeout(
                        action(() => {
                            croppingProto.data = new ImageField(returnedfilename);
                        }),
                        500
                    )
                );
            })
            .catch(function (error: any) {
                console.error('oops, something went wrong!', error);
            });

        return cropping;
    };

    updateIcon = () => {
        // currently we render pdf icons as text labels
        const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
        const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
        this._pdfViewer?._mainCont.current &&
            CollectionFreeFormView.UpdateIcon(
                filename,
                docViewContent,
                this.layoutDoc[Width](),
                this.layoutDoc[Height](),
                this.props.PanelWidth(),
                this.props.PanelHeight(),
                NumCast(this.layoutDoc._layout_scrollTop),
                NumCast(this.rootDoc[this.fieldKey + '_nativeHeight'], 1),
                true,
                this.layoutDoc[Id] + '-icon',
                (iconFile: string, nativeWidth: number, nativeHeight: number) => {
                    setTimeout(() => {
                        this.dataDoc.icon = new ImageField(iconFile);
                        this.dataDoc['icon_nativeWidth'] = nativeWidth;
                        this.dataDoc['icon_nativeHeight'] = nativeHeight;
                    }, 500);
                }
            );
    };

    componentWillUnmount() {
        Object.values(this._disposers).forEach(disposer => disposer?.());
    }
    componentDidMount() {
        this.props.setContentView?.(this);
        this._disposers.select = reaction(
            () => this.props.isSelected(),
            () => {
                document.removeEventListener('keydown', this.onKeyDown);
                this.props.isSelected(true) && document.addEventListener('keydown', this.onKeyDown);
            },
            { fireImmediately: true }
        );
        this._disposers.scroll = reaction(
            () => this.rootDoc.layout_scrollTop,
            () => {
                if (!(ComputedField.WithoutComputed(() => FieldValue(this.props.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
                    this.props.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
                }
                this.props.Document[this.SidebarKey + '_freeform_scale'] = 1;
                this.props.Document[this.SidebarKey + '_freeform_panX'] = 0;
            }
        );
    }

    brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._pdfViewer?.brushView(view);

    sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
        if (DocListCast(this.props.Document[this.props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
            this.toggleSidebar(false);
            return true;
        }
        return this.props.addDocTab(doc, where);
    };
    focus = (anchor: Doc, options: DocFocusOptions) => {
        this._initialScrollTarget = anchor;
        return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.presViewScroll)), options);
    };

    getView = async (doc: Doc) => {
        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
        return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
    };

    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
        let ele: Opt<HTMLDivElement> = undefined;
        if (this._pdfViewer?.selectionContent()) {
            ele = document.createElement('div');
            ele.append(this._pdfViewer.selectionContent()!);
        }
        const docAnchor = () =>
            Docs.Create.ConfigDocument({
                title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)),
                annotationOn: this.rootDoc,
            });
        const annoAnchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations(), true);
        const anchor = annoAnchor ?? docAnchor();
        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.rootDoc);
        anchor.text = ele?.textContent ?? '';
        anchor.textHtml = ele?.innerHTML;
        if (addAsAnnotation || annoAnchor) {
            this.addDocument(anchor);
        }
        return anchor;
    };

    @action
    loaded = (nw: number, nh: number, np: number) => {
        this.dataDoc[this.props.fieldKey + '_numPages'] = np;
        Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72));
        Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72);
        this.layoutDoc._height = this.layoutDoc[Width]() / (Doc.NativeAspect(this.dataDoc) || 1);
        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
    };

    public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => {
        if (!this._searching && !clear) {
            this._searching = true;
            setTimeout(() => {
                this._searchRef.current?.focus();
                this._searchRef.current?.select();
                this._searchRef.current?.setRangeText(searchString);
            });
        }
        return this._pdfViewer?.search(searchString, bwd, clear) || false;
    });
    public prevAnnotation = () => this._pdfViewer?.prevAnnotation();
    public nextAnnotation = () => this._pdfViewer?.nextAnnotation();
    public backPage = () => {
        this.Document._layout_curPage = Math.max(1, (NumCast(this.Document._layout_curPage) || 1) - 1);
        return true;
    };
    public forwardPage = () => {
        this.Document._layout_curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + '_numPages']), (NumCast(this.Document._layout_curPage) || 1) + 1);
        return true;
    };
    public gotoPage = (p: number) => (this.Document._layout_curPage = p);

    @undoBatch
    onKeyDown = action((e: KeyboardEvent) => {
        let processed = false;
        switch (e.key) {
            case 'PageDown':
                processed = this.forwardPage();
                break;
            case 'PageUp':
                processed = this.backPage();
                break;
        }
        if (processed) {
            e.stopImmediatePropagation();
            e.preventDefault();
        }
    });

    setPdfViewer = (pdfViewer: PDFViewer) => {
        this._pdfViewer = pdfViewer;
        const docView = this.props.DocumentView?.();
        if (this._initialScrollTarget && docView) {
            this.focus(this._initialScrollTarget, { instant: true });
            this._initialScrollTarget = undefined;
        }
    };
    searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);

    // adding external documents; to sidebar key
    // if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"_annotation")
    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
        if (!this.layoutDoc._show_sidebar) this.toggleSidebar();
        return this.addDocument(doc, sidebarKey);
    };
    sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => {
        // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf
        const batch = UndoManager.StartBatch('sidebar');
        setupMoveUpEvents(
            this,
            e,
            (e, down, delta) => {
                const localDelta = this.props
                    .ScreenToLocalTransform()
                    .scale(this.props.NativeDimScaling?.() || 1)
                    .transformDirection(delta[0], delta[1]);
                const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
                const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
                const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
                if (ratio >= 1) {
                    this.layoutDoc.nativeWidth = nativeWidth * ratio;
                    onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]);
                    this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                }
                return false;
            },
            (e, movement, isClick) => !isClick && batch.end(),
            () => {
                onButton && this.toggleSidebar();
                batch.end();
            }
        );
    };
    @observable _previewNativeWidth: Opt<number> = undefined;
    @observable _previewWidth: Opt<number> = undefined;
    toggleSidebar = action((preview: boolean = false) => {
        const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
        const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
        const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth + PDFBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth;
        const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
        if (preview) {
            this._previewNativeWidth = nativeWidth * sideratio;
            this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth;
            this._showSidebar = true;
        } else {
            this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
            this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth;
            this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
        }
    });
    settingsPanel() {
        const pageBtns = (
            <>
                <button className="pdfBox-backBtn" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={this.backPage}>
                    <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-left'} size="sm" />
                </button>
                <button className="pdfBox-fwdBtn" key="fwd" title="Page Forward" onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}>
                    <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-right'} size="sm" />
                </button>
            </>
        );
        const searchTitle = `${!this._searching ? 'Open' : 'Close'} Search Bar`;
        const curPage = NumCast(this.Document._layout_curPage) || 1;
        return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? null : (
            <div
                className="pdfBox-ui"
                onKeyDown={e => ([KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true)}
                onPointerDown={e => e.stopPropagation()}
                style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}>
                <div className="pdfBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
                    <button className="pdfBox-overlayButton" title={searchTitle} />
                    <input
                        className="pdfBox-searchBar"
                        placeholder="Search"
                        ref={this._searchRef}
                        onChange={this.searchStringChanged}
                        onKeyDown={e => {
                            e.stopPropagation();
                            e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey);
                        }}
                    />
                    <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
                        <FontAwesomeIcon icon="search" size="sm" />
                    </button>
                    <button className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}>
                        <FontAwesomeIcon icon={'arrow-up'} size="lg" />
                    </button>
                    <button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}>
                        <FontAwesomeIcon icon={'arrow-down'} size="lg" />
                    </button>
                </div>
                <button
                    className="pdfBox-overlayButton"
                    title={searchTitle}
                    onClick={action(() => {
                        this._searching = !this._searching;
                        this.search('', true, true);
                    })}>
                    <div className="pdfBox-overlayButton-arrow" onPointerDown={e => e.stopPropagation()} />
                    <div className="pdfBox-overlayButton-iconCont" onPointerDown={e => e.stopPropagation()}>
                        <FontAwesomeIcon icon={this._searching ? 'times' : 'search'} size="lg" />
                    </div>
                </button>

                <div className="pdfBox-pageNums">
                    <input
                        value={curPage}
                        style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: 'all' }}
                        onChange={e => (this.Document._layout_curPage = Number(e.currentTarget.value))}
                        onKeyDown={e => e.stopPropagation()}
                        onClick={action(() => (this._pageControls = !this._pageControls))}
                    />
                    {this._pageControls ? pageBtns : null}
                </div>
                {this.sidebarHandle}
            </div>
        );
    }
    sidebarWidth = () => {
        if (!this.SidebarShown) return 0;
        if (this._previewWidth) return PDFBox.sidebarResizerWidth + PDFBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target)
        const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc);
        return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
    };
    @undoBatch
    toggleSidebarType = () => (this.rootDoc.sidebar_collectionType = this.rootDoc.sidebar_collectionType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
    specificContextMenu = (e: React.MouseEvent): void => {
        const cm = ContextMenu.Instance;
        const options = cm.findByDescription('Options...');
        const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
        optionItems.push({ description: 'Toggle Sidebar Type', event: this.toggleSidebarType, icon: 'expand-arrows-alt' });
        !Doc.noviceMode && optionItems.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
        //optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
        !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' });
        const help = cm.findByDescription('Help...');
        const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
        helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
        !help && ContextMenu.Instance.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'asterisk' });
    };

    @computed get renderTitleBox() {
        const classname = 'pdfBox' + (this.props.isContentActive() ? '-interactive' : '');
        return (
            <div className={classname}>
                <div className="pdfBox-title-outer">
                    <strong className="pdfBox-title">{StrCast(this.props.Document.title)}</strong>
                </div>
            </div>
        );
    }

    anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
    @observable _showSidebar = false;
    @computed get SidebarShown() {
        return this._showSidebar || this.layoutDoc._show_sidebar ? true : false;
    }
    @computed get sidebarHandle() {
        return (
            <div
                className="pdfBox-sidebarBtn"
                key="sidebar"
                title="Toggle Sidebar"
                style={{
                    display: !this.props.isContentActive() ? 'none' : undefined,
                    top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5,
                    backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                }}
                onPointerDown={e => this.sidebarBtnDown(e, true)}>
                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
            </div>
        );
    }

    public get SidebarKey() {
        return this.fieldKey + '_sidebar';
    }
    @computed get pdfScale() {
        const pdfNativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
        const nativeWidth = NumCast(this.layoutDoc.nativeWidth, pdfNativeWidth);
        const pdfRatio = pdfNativeWidth / nativeWidth;
        return (pdfRatio * this.props.PanelWidth()) / pdfNativeWidth;
    }
    @computed get sidebarNativeWidth() {
        return this.sidebarWidth() / this.pdfScale;
    }
    @computed get sidebarNativeHeight() {
        return this.props.PanelHeight() / this.pdfScale;
    }
    sidebarNativeWidthFunc = () => this.sidebarNativeWidth;
    sidebarNativeHeightFunc = () => this.sidebarNativeHeight;
    sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
    sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate((this.sidebarWidth() - this.props.PanelWidth()) / this.pdfScale, 0);
    @computed get sidebarCollection() {
        const renderComponent = (tag: string) => {
            const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : CollectionStackingView;
            return ComponentTag === CollectionStackingView ? (
                <SidebarAnnos
                    ref={this._sidebarRef}
                    {...this.props}
                    rootDoc={this.rootDoc}
                    layoutDoc={this.layoutDoc}
                    dataDoc={this.dataDoc}
                    setHeight={emptyFunction}
                    nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
                    showSidebar={this.SidebarShown}
                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                    sidebarAddDocument={this.sidebarAddDocument}
                    moveDocument={this.moveDocument}
                    removeDocument={this.removeDocument}
                />
            ) : (
                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
                    <ComponentTag
                        {...this.props}
                        setContentView={emptyFunction} // override setContentView to do nothing
                        NativeWidth={this.sidebarNativeWidthFunc}
                        NativeHeight={this.sidebarNativeHeightFunc}
                        PanelHeight={this.props.PanelHeight}
                        PanelWidth={this.sidebarWidth}
                        xPadding={0}
                        yPadding={0}
                        viewField={this.SidebarKey}
                        isAnnotationOverlay={false}
                        originTopLeft={true}
                        isAnyChildContentActive={this.isAnyChildContentActive}
                        select={emptyFunction}
                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                        removeDocument={this.sidebarRemDocument}
                        moveDocument={this.sidebarMoveDocument}
                        addDocument={this.sidebarAddDocument}
                        ScreenToLocalTransform={this.sidebarScreenToLocal}
                        renderDepth={this.props.renderDepth + 1}
                        noSidebar={true}
                        fieldKey={this.SidebarKey}
                    />
                </div>
            );
        };
        return (
            <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: '100%', right: 0, backgroundColor: `white` }}>
                {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))}
            </div>
        );
    }
    isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected() || (this.props.renderDepth === 0 && LightboxView.IsLightboxDocView(this.props.docViewPath()));
    @computed get renderPdfView() {
        TraceMobx();
        const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
        const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
        return !this._pdf ? null : (
            <div
                className="pdfBox"
                onContextMenu={this.specificContextMenu}
                style={{
                    display: this.props.thumbShown?.() ? 'none' : undefined,
                    height: this.props.Document._layout_scrollTop && !this.Document._layout_fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this.props.PanelWidth()) / NumCast(this.Document._width) : undefined,
                }}>
                <div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
                <div
                    style={{
                        width: `calc(${100 / scale}% - ${(this.sidebarWidth() / scale) * (this._previewWidth ? scale : 1)}px)`,
                        height: `${100 / scale}%`,
                        transform: `scale(${scale})`,
                        position: 'absolute',
                        transformOrigin: 'top left',
                        top: 0,
                    }}>
                    <PDFViewer
                        {...this.props}
                        sidebarAddDoc={this.sidebarAddDocument}
                        rootDoc={this.rootDoc}
                        addDocTab={this.sidebarAddDocTab}
                        layoutDoc={this.layoutDoc}
                        dataDoc={this.dataDoc}
                        pdf={this._pdf}
                        focus={this.focus}
                        url={this.pdfUrl!.url.pathname}
                        isContentActive={this.isPdfContentActive}
                        anchorMenuClick={this.anchorMenuClick}
                        loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined}
                        setPdfViewer={this.setPdfViewer}
                        addDocument={this.addDocument}
                        moveDocument={this.moveDocument}
                        removeDocument={this.removeDocument}
                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                        crop={this.crop}
                    />
                </div>
                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>{this.sidebarCollection}</div>
                {this.settingsPanel()}
            </div>
        );
    }

    static pdfcache = new Map<string, Pdfjs.PDFDocumentProxy>();
    static pdfpromise = new Map<string, Promise<Pdfjs.PDFDocumentProxy>>();
    render() {
        TraceMobx();
        if (this.props.thumbShown?.()) return null;
        const pdfView = this.renderPdfView;

        const href = this.pdfUrl?.url.href;
        if (!pdfView && href) {
            if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href))));
            else {
                if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise);
                PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf))));
            }
        }
        return pdfView ?? this.renderTitleBox;
    }
}
