import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconButton } from 'browndash-components';
import { black } from 'colors';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
import * as React from 'react';
import { ClientUtils, returnFalse, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, FieldValue, ImageCast, NumCast, StrCast, toList } from '../../../fields/Types';
import { ImageField, PdfField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { DocUtils } from '../../documents/DocUtils';
import { KeyCodes } from '../../util/KeyCodes';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { CollectionStackingView } from '../collections/CollectionStackingView';
import { ContextMenu } from '../ContextMenu';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { Colors } from '../global/globalEnums';
import { PDFViewer } from '../pdf/PDFViewer';
import { PinDocView, PinProps } from '../PinFuncs';
import { SidebarAnnos } from '../SidebarAnnos';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import { ImageBox } from './ImageBox';
import { OpenWhere } from './OpenWhere';
import './PDFBox.scss';
import { CreateImage } from './WebBoxRenderer';

@observer
export class PDFBox extends ViewBoxAnnotatableComponent<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> = undefined;
    @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: FieldViewProps) {
        super(props);
        makeObservable(this);
        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 = NumCast(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 => {
                        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) => {
        const docViewContent = this.DocumentView?.().ContentDiv;
        if (!region || !docViewContent) return undefined;
        const cropping = Doc.MakeCopy(region, true);
        cropping.layout_unrendered = false; // text selection have this
        cropping.text_inlineAnnotations = undefined; // text selections have this -- it causes them not to be rendered.
        cropping.backgroundColor = undefined; // text selections have this -- it causes images to be fully transparent
        cropping.opacity = undefined; // text selections have this -- it causes images to be fully transparent
        const regionData = region[DocData];
        regionData.lockedPosition = true;
        regionData.title = 'region:' + this.Document.title;
        regionData.followLinkToggle = true;
        this.addDocument(region);

        const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
        newDiv.style.width = NumCast(this.layoutDoc._width).toString();
        newDiv.style.height = NumCast(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 = NumCast(cropping._width) * (this._props.NativeDimScaling?.() || 1);
        const anchh = NumCast(cropping._height) * (this._props.NativeDimScaling?.() || 1);
        // const viewScale = 1;
        cropping.title = 'crop: ' + this.Document.title;
        cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
        cropping.y = NumCast(this.Document.y);
        cropping._width = anchw;
        cropping._height = anchh;
        cropping.onClick = undefined;
        const croppingProto = cropping[DocData];
        croppingProto.annotationOn = undefined;
        croppingProto.isDataDoc = true;
        croppingProto.proto = Cast(this.Document.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(ClientUtils.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.dataDoc[this.fieldKey + '_nativeWidth']),
            (NumCast(region.x) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
            4
        )
            .then(dataUrl => {
                ClientUtils.convertDataUri(dataUrl, region[Id]).then(returnedfilename =>
                    setTimeout(
                        action(() => {
                            croppingProto.data = new ImageField(returnedfilename);
                        }),
                        500
                    )
                );
            })
            .catch(error => {
                console.error('oops, something went wrong!', error);
            });

        return cropping;
    };

    updateIcon = () => {
        // currently we render pdf icons as text labels
        const docViewContent = this.DocumentView?.().ContentDiv;
        const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
        this._pdfViewer?._mainCont.current &&
            docViewContent &&
            UpdateIcon(
                filename,
                docViewContent,
                NumCast(this.layoutDoc._width),
                NumCast(this.layoutDoc._height),
                this._props.PanelWidth(),
                this._props.PanelHeight(),
                NumCast(this.layoutDoc._layout_scrollTop),
                NumCast(this.dataDoc[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.setContentViewBox?.(this);
        this._disposers.select = reaction(
            () => this._props.isSelected(),
            () => {
                document.removeEventListener('keydown', this.onKeyDown);
                this._props.isSelected() && document.addEventListener('keydown', this.onKeyDown);
            },
            { fireImmediately: true }
        );
        this._disposers.scroll = reaction(
            () => this.layoutDoc.layout_scrollTop,
            () => {
                if (!(ComputedField.WithoutComputed(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
                    this.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
                }
                this.layoutDoc[this.SidebarKey + '_freeform_scale'] = 1;
                this.layoutDoc[this.SidebarKey + '_freeform_panX'] = 0;
            }
        );
    }

    sidebarAddDocTab = (docIn: Doc | Doc[], where: OpenWhere) => {
        const docs = toList(docIn);
        if (docs.some(doc => DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc)) && !this.SidebarShown) {
            this.toggleSidebar(false);
            return true;
        }
        return this._props.addDocTab(docs, where);
    };
    focus = (anchor: Doc, options: FocusViewOptions) => {
        this._initialScrollTarget = anchor;
        return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options);
    };

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

    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
        let ele: Opt<HTMLDivElement>;
        if (this._pdfViewer?.selectionContent()) {
            ele = document.createElement('div');
            ele.append(this._pdfViewer.selectionContent()!);
        }
        const docAnchor = () =>
            Docs.Create.ConfigDocument({
                title: StrCast(this.Document.title + '@' + (NumCast(this.layoutDoc._layout_scrollTop) ?? 0).toFixed(0)),
                annotationOn: this.Document,
            });
        const visibleAnchor = this._pdfViewer?._getAnchor?.(this._pdfViewer.savedAnnotations(), true);
        const anchor = visibleAnchor ?? docAnchor();
        PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.Document);
        anchor.text = ele?.textContent ?? '';
        anchor.text_html = ele?.innerHTML;
        addAsAnnotation && 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 = NumCast(this.layoutDoc._width) / (Doc.NativeAspect(this.dataDoc) || 1);
        !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
    };

    override 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;
            default:
        }
        if (processed) {
            e.stopImmediatePropagation();
            e.preventDefault();
        }
    });

    setPdfViewer = (pdfViewer: PDFViewer) => {
        this._pdfViewer = pdfViewer;
        if (this._initialScrollTarget) {
            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,
            (moveEv, 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 = NumCast(this.layoutDoc._width) + localDelta[0]);
                    this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                }
                return false;
            },
            (clickEv, 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) + NumCast(this.layoutDoc._width)) / NumCast(this.layoutDoc._width);
        const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
        if (preview) {
            this._previewNativeWidth = nativeWidth * sideratio;
            this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth;
            this._showSidebar = true;
        } else {
            this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
            this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth;
            this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
        }
    });
    settingsPanel() {
        const pageBtns = (
            <>
                <button type="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 type="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 type="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 type="button" className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
                        <FontAwesomeIcon icon="search" size="sm" />
                    </button>
                    <button type="button" className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}>
                        <FontAwesomeIcon icon="arrow-up" size="lg" />
                    </button>
                    <button type="button" className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}>
                        <FontAwesomeIcon icon="arrow-down" size="lg" />
                    </button>
                </div>
                <button
                    type="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.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform;
    };
    specificContextMenu = (): void => {
        const cm = ContextMenu.Instance;
        const options = cm.findByDescription('Options...');
        const optionItems = options?.subitems ?? [];

        !Doc.noviceMode && 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 = help?.subitems ?? [];
        helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && ClientUtils.CopyText(ClientUtils.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.Document.title)}</strong>
                </div>
            </div>
        );
    }

    anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
    @observable _showSidebar = false;
    @computed get SidebarShown() {
        return !!(this._showSidebar || this.layoutDoc._show_sidebar);
    }
    @computed get sidebarHandle() {
        return (
            <div className="pdfBox-sidebarBtn-container">
                <div
                    className="pdfBox-sidebarBtn"
                    key="sidebar"
                    title="Toggle Sidebar"
                    style={{
                        display: !this._props.isContentActive() ? 'none' : undefined,
                        top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
                        backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                    }}>
                    {/* // onPointerDown={e => this.sidebarBtnDown(e, true)} */}
                    <IconButton tooltip="Toggle Annotation Palette" icon={<FontAwesomeIcon style={{ color: Colors.WHITE }} icon="palette" />} onPointerDown={e => this.sidebarBtnDown(e, true)} />
                </div>
                <div
                    className="pdfBox-sidebarBtn"
                    key="sidebar"
                    title="Toggle Sidebar"
                    style={{
                        display: !this._props.isContentActive() ? 'none' : undefined,
                        top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
                        backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                    }}>
                    <IconButton tooltip="Toggle Sidebar" icon={<FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />} onPointerDown={e => this.sidebarBtnDown(e, true)} />
                </div>
            </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.ScreenToLocalBoxXf().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}
                    // eslint-disable-next-line react/jsx-props-no-spreading
                    {...this._props}
                    Document={this.Document}
                    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, () => this._props.select(false), true)}>
                    <ComponentTag
                        // eslint-disable-next-line react/jsx-props-no-spreading
                        {...this._props}
                        setContentViewBox={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
                        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
                        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[this.SidebarKey + '_type_collection']))}
            </div>
        );
    }
    @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={{
                    height: this.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
                        // eslint-disable-next-line react/jsx-props-no-spreading
                        {...this._props}
                        pdfBox={this}
                        sidebarAddDoc={this.sidebarAddDocument}
                        addDocTab={this.sidebarAddDocTab}
                        layoutDoc={this.layoutDoc}
                        dataDoc={this.dataDoc}
                        pdf={this._pdf}
                        focus={this.focus}
                        url={this.pdfUrl!.url.pathname}
                        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();
        const pdfView = !this._pdf ? null : 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 => {
                        PDFBox.pdfcache.set(href, (this._pdf = pdf));
                    })
                );
            }
        }
        return pdfView ?? this.renderTitleBox;
    }
}

Docs.Prototypes.TemplateMap.set(DocumentType.PDF, {
    layout: { view: PDFBox, dataField: 'data' },
    options: { acl: '', _layout_curPage: 1, _layout_fitWidth: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' },
});
