import { action, computed, IReactionDisposer, observable, ObservableMap, 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, Field, Opt } from '../../../fields/Doc';
import { Height } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SharingManager } from '../../util/SharingManager';
import { SnappingManager } from '../../util/SnappingManager';
import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
import { StyleProp } from '../StyleProvider';
import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import './PDFViewer.scss';
import React = require('react');
import { GPTPopup } from './GPTPopup/GPTPopup';
const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer');
const pdfjsLib = require('pdfjs-dist');
const _global = (window /* browser */ || global) /* node */ as any;

//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
// The workerSrc property shall be specified.
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.16.105/build/pdf.worker.js';

interface IViewerProps extends FieldViewProps {
    Document: Doc;
    rootDoc: Doc;
    dataDoc: Doc;
    layoutDoc: Doc;
    fieldKey: string;
    pdf: Pdfjs.PDFDocumentProxy;
    url: string;
    sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean;
    loaded?: (nw: number, nh: number, np: number) => void;
    setPdfViewer: (view: PDFViewer) => void;
    anchorMenuClick?: () => undefined | ((anchor: Doc, summarize?: boolean) => void);
    crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
}

/**
 * Handles rendering and virtualization of the pdf
 */
@observer
export class PDFViewer extends React.Component<IViewerProps> {
    static _annotationStyle: any = addStyleSheet();
    @observable private _pageSizes: { width: number; height: number }[] = [];
    @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
    @observable private _marqueeing: number[] | undefined;
    @observable private _textSelecting = true;
    @observable private _showWaiting = true;
    @observable private _overlayAnnoInfo: Opt<Doc>;
    @observable private Index: number = -1;

    private _pdfViewer: any;
    private _styleRule: any; // stylesheet rule for making hyperlinks clickable
    private _retries = 0; // number of times tried to create the PDF viewer
    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
    private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }) => void);
    private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
    private _disposers: { [name: string]: IReactionDisposer } = {};
    private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
    _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
    private _selectionText: string = '';
    private _selectionContent: DocumentFragment | undefined;
    private _downX: number = 0;
    private _downY: number = 0;
    private _lastSearch = false;
    private _viewerIsSetup = false;
    private _ignoreScroll = false;
    private _initialScroll: { loc: Opt<number>; easeFunc: 'linear' | 'ease' | undefined } | undefined;
    private _forcedScroll = true;
    get _getAnchor() {
        return AnchorMenu.Instance?.GetAnchor;
    }

    selectionText = () => this._selectionText;
    selectionContent = () => this._selectionContent;

    @observable isAnnotating = false;
    // key where data is stored
    @computed get allAnnotations() {
        return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.props.childFilters(), this.props.childFiltersByRanges());
    }
    @computed get inlineTextAnnotations() {
        return this.allAnnotations.filter(a => a.textInlineAnnotations);
    }

    componentDidMount = async () => {
        runInAction(() => (this._showWaiting = true));
        this.setupPdfJsViewer();
        this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));

        this._disposers.layout_autoHeight = reaction(
            () => this.props.layoutDoc._layout_autoHeight,
            layout_autoHeight => {
                if (layout_autoHeight) {
                    this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']);
                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
                }
            }
        );

        this._disposers.selected = reaction(
            () => this.props.isSelected(),
            selected => {
                // if (!selected) {
                //     Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
                //     Array.from(this._savedAnnotations.keys()).forEach(k => this._savedAnnotations.set(k, []));
                // }
                SelectionManager.Views().length === 1 && this.setupPdfJsViewer();
            },
            { fireImmediately: true }
        );
        this._disposers.curPage = reaction(
            () => Cast(this.props.Document._layout_curPage, 'number', null),
            page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
            { fireImmediately: true }
        );
    };

    componentWillUnmount = () => {
        Object.values(this._disposers).forEach(disposer => disposer?.());
        document.removeEventListener('copy', this.copy);
    };

    copy = (e: ClipboardEvent) => {
        if (this.props.isContentActive() && e.clipboardData) {
            e.clipboardData.setData('text/plain', this._selectionText);
            const anchor = this._getAnchor(undefined, false);
            if (anchor) {
                anchor.textCopied = true;
                e.clipboardData.setData('dash/pdfAnchor', anchor[Id]);
            }
            e.preventDefault();
        }
    };

    @observable _scrollHeight = 0;

    @action
    initialLoad = async () => {
        if (this._pageSizes.length === 0) {
            this._pageSizes = Array<{ width: number; height: number }>(this.props.pdf.numPages);
            await Promise.all(
                this._pageSizes.map((val, i) =>
                    this.props.pdf.getPage(i + 1).then(
                        action((page: Pdfjs.PDFPageProxy) => {
                            const page0or180 = page.rotate === 0 || page.rotate === 180;
                            this._pageSizes.splice(i, 1, {
                                width: page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1],
                                height: page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0],
                            });
                            if (i === this.props.pdf.numPages - 1) {
                                this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this.props.pdf.numPages);
                            }
                        })
                    )
                )
            );
        }
        runInAction(() => (this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72));
    };

    _scrollStopper: undefined | (() => void);

    // scrolls to focus on a nested annotation document.  if this is part a link preview then it will jump to the scroll location,
    // otherwise it will scroll smoothly.
    scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => {
        const mainCont = this._mainCont.current;
        let focusSpeed: Opt<number>;
        if (doc !== this.props.rootDoc && mainCont) {
            const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
            const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this.props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
            if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._layout_scrollTop) {
                if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
                else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
                else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) });
            }
        } else {
            this._initialScroll = { loc: NumCast(this.props.layoutDoc._layout_scrollTop), easeFunc: options.easeFunc };
        }
        return focusSpeed;
    };
    crop = (region: Doc | undefined, addCrop?: boolean) => this.props.crop(region, addCrop);
    brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view);

    @action
    setupPdfJsViewer = async () => {
        if (this._viewerIsSetup) return;
        this._viewerIsSetup = true;
        this._showWaiting = true;
        this.props.setPdfViewer(this);
        await this.initialLoad();

        this.createPdfViewer();
    };

    pagesinit = () => {
        if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
            runInAction(() => (this._pdfViewer.currentScaleValue = this.props.layoutDoc._freeform_scale = 1));
            this.gotoPage(NumCast(this.props.Document._layout_curPage, 1));
        }
        document.removeEventListener('pagesinit', this.pagesinit);
        var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
        this._disposers.scale = reaction(
            () => NumCast(this.props.layoutDoc._freeform_scale, 1),
            scale => (this._pdfViewer.currentScaleValue = scale),
            { fireImmediately: true }
        );
        this._disposers.scroll = reaction(
            () => Math.abs(NumCast(this.props.Document._layout_scrollTop)),
            pos => {
                if (!this._ignoreScroll) {
                    this._showWaiting && this.setupPdfJsViewer();
                    const viewTrans = quickScroll?.loc ?? StrCast(this.props.Document._viewTransition);
                    const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                    const durationSecStr = viewTrans.match(/([0-9.]*)s/);
                    const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
                    this._forcedScroll = true;
                    if (duration) {
                        setTimeout(
                            () => {
                                this._mainCont.current && (this._scrollStopper = smoothScroll(duration, this._mainCont.current, pos, this._initialScroll?.easeFunc ?? 'ease', this._scrollStopper));
                                setTimeout(() => (this._forcedScroll = false), duration);
                            },
                            this._mainCont.current ? 0 : 250
                        ); // wait for mainCont and try again to scroll
                    } else {
                        this._mainCont.current?.scrollTo({ top: pos });
                        this._forcedScroll = false;
                    }
                }
            },
            { fireImmediately: true }
        );
        quickScroll = undefined;
        if (this._initialScroll !== undefined && this._mainCont.current) {
            this._mainCont.current?.scrollTo({ top: Math.abs(this._initialScroll?.loc || 0) });
            this._initialScroll = undefined;
        }
    };

    createPdfViewer() {
        if (!this._mainCont.current) {
            // bcz: I don't think this is ever triggered or needed
            console.log('PDFViewer- I guess we got here');
            if (this._retries < 5) {
                this._retries++;
                console.log('PDFViewer- retry num:' + this._retries);
                setTimeout(() => this.createPdfViewer(), 1000);
            }
            return;
        }
        document.removeEventListener('copy', this.copy);
        document.addEventListener('copy', this.copy);
        const eventBus = new PDFJSViewer.EventBus(true);
        eventBus._on('pagesinit', this.pagesinit);
        eventBus._on(
            'pagerendered',
            action(() => (this._showWaiting = false))
        );
        const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
        const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
        this._pdfViewer = new PDFJSViewer.PDFViewer({
            container: this._mainCont.current,
            viewer: this._viewer.current,
            linkService: pdfLinkService,
            findController: pdfFindController,
            renderer: 'canvas',
            eventBus,
        });
        pdfLinkService.setViewer(this._pdfViewer);
        pdfLinkService.setDocument(this.props.pdf, null);
        this._pdfViewer.setDocument(this.props.pdf);
    }

    @action
    prevAnnotation = () => {
        this.Index = Math.max(this.Index - 1, 0);
        this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]);
    };

    @action
    nextAnnotation = () => {
        this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1);
        this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]);
    };

    @action
    gotoPage = (p: number) => {
        this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
    };

    @action
    scrollToAnnotation = (scrollToAnnotation: Doc) => {
        if (scrollToAnnotation) {
            this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), { zoomTime: 500 });
            Doc.linkFollowHighlight(scrollToAnnotation);
        }
    };

    @observable private _scrollTimer: any;

    onScroll = (e: React.UIEvent<HTMLElement>) => {
        if (this._mainCont.current && !this._forcedScroll) {
            this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
            if (!LinkDocPreview.LinkInfo) {
                this.props.layoutDoc._layout_scrollTop = this._mainCont.current.scrollTop;
            }
            this._ignoreScroll = false;
            if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio
            this._scrollTimer = setTimeout(() => {
                DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!(true)!, false);
                this._scrollTimer = undefined;
            }, 200);
        }
    };

    // get the page index that the vertical offset passed in is on
    getPageFromScroll = (vOffset: number) => {
        let index = 0;
        let currOffset = vOffset;
        while (index < this._pageSizes.length && this._pageSizes[index] && currOffset - this._pageSizes[index].height > 0) {
            currOffset -= this._pageSizes[index++].height;
        }
        return index;
    };

    @action
    search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
        const findOpts = {
            caseSensitive: false,
            findPrevious: bwd,
            highlightAll: true,
            phraseSearch: true,
            query: searchString,
        };
        if (clear) {
            this._pdfViewer?.eventBus.dispatch('reset', {});
        } else if (!searchString) {
            bwd ? this.prevAnnotation() : this.nextAnnotation();
        } else if (this._pdfViewer?.pageViewsReady) {
            this._pdfViewer?.eventBus.dispatch('find', { ...findOpts, type: 'again' });
        } else if (this._mainCont.current) {
            const executeFind = () => this._pdfViewer?.eventBus.dispatch('find', findOpts);
            this._mainCont.current.addEventListener('pagesloaded', executeFind);
            this._mainCont.current.addEventListener('pagerendered', executeFind);
        }
        return true;
    };

    @action
    onPointerDown = (e: React.PointerEvent): void => {
        // const hit = document.elementFromPoint(e.clientX, e.clientY);
        // bcz: Change. drag selecting requires that preventDefault is NOT called.  This used to happen in DocumentView,
        //      but that's changed, so this shouldn't be needed.
        // if (hit && hit.localName === "span" && this.annotationsActive(true)) {  // drag selecting text stops propagation
        //     e.button === 0 && e.stopPropagation();
        // }
        // if alt+left click, drag and annotate
        this._downX = e.clientX;
        this._downY = e.clientY;
        if ((this.props.Document._freeform_scale || 1) !== 1) return;
        if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
            this._setPreviewCursor?.(e.clientX, e.clientY, true, false);
        }
        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
            this.props.select(false);
            MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
            this._marqueeing = [e.clientX, e.clientY];
            this.isAnnotating = true;
            const target = e.target as any;
            if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
                this._textSelecting = false;
            } else {
                // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
                setTimeout(
                    action(() => (this._marqueeing = undefined)),
                    100
                ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.

                this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' });
                document.addEventListener('pointerup', this.onSelectEnd);
            }
        }
    };

    @action
    finishMarquee = (x?: number, y?: number) => {
        this.isAnnotating = false;
        this._marqueeing = undefined;
        this._textSelecting = true;
    };

    @action
    onSelectEnd = (e: PointerEvent): void => {
        this.isAnnotating = false;
        clearStyleSheetRules(PDFViewer._annotationStyle);
        this.props.select(false);
        document.removeEventListener('pointerup', this.onSelectEnd);

        const sel = window.getSelection();
        if (sel) {
            AnchorMenu.Instance.setSelectedText(sel.toString());
        }

        if (sel?.type === 'Range') {
            this.createTextAnnotation(sel, sel.getRangeAt(0));
            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
        }

        // Changing which document to add the annotation to (the currently selected PDF)
        GPTPopup.Instance.setSidebarId('data_sidebar');
        GPTPopup.Instance.addDoc = this.props.sidebarAddDoc;
    };

    @action
    createTextAnnotation = (sel: Selection, selRange: Range) => {
        if (this._mainCont.current) {
            const boundingRect = this._mainCont.current.getBoundingClientRect();
            const clientRects = selRange.getClientRects();
            for (let i = 0; i < clientRects.length; i++) {
                const rect = clientRects.item(i);
                if (rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) {
                    const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
                    const pdfScale = NumCast(this.props.layoutDoc._freeform_scale, 1);
                    const annoBox = document.createElement('div');
                    annoBox.className = 'marqueeAnnotator-annotationBox';
                    // transforms the positions from screen onto the pdf div
                    annoBox.style.top = (((rect.top - boundingRect.top) * scaleX) / pdfScale + this._mainCont.current.scrollTop).toString();
                    annoBox.style.left = (((rect.left - boundingRect.left) * scaleX) / pdfScale).toString();
                    annoBox.style.width = ((rect.width * this._mainCont.current.offsetWidth) / boundingRect.width / pdfScale).toString();
                    annoBox.style.height = ((rect.height * this._mainCont.current.offsetHeight) / boundingRect.height / pdfScale).toString();
                    this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top));
                }
            }
        }
        this._selectionContent = selRange.cloneContents();
        this._selectionText = this._selectionContent?.textContent || '';

        // clear selection
        if (sel.empty) {
            // Chrome
            sel.empty();
        } else if (sel.removeAllRanges) {
            // Firefox
            sel.removeAllRanges();
        }
    };

    onClick = (e: React.MouseEvent) => {
        this._scrollStopper?.();
        if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
            this._setPreviewCursor(e.clientX, e.clientY, false, false);
        }
        // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks
    };

    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
    setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func);

    @action
    onZoomWheel = (e: React.WheelEvent) => {
        if (this.props.isContentActive(true)) {
            e.stopPropagation();
            if (e.ctrlKey) {
                const curScale = Number(this._pdfViewer.currentScaleValue);
                this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - (curScale * e.deltaY) / 1000));
                this.props.layoutDoc._freeform_scale = Number(this._pdfViewer.currentScaleValue);
            }
        }
    };

    pointerEvents = () => (this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none');
    @computed get annotationLayer() {
        return (
            <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
                {this.inlineTextAnnotations
                    .sort((a, b) => NumCast(a.y) - NumCast(b.y))
                    .filter(anno => !anno.hidden)
                    .map(anno => (
                        <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                    ))}
            </div>
        );
    }

    @computed get overlayInfo() {
        return !this._overlayAnnoInfo ? null : (
            <div className="pdfViewerDash-overlayAnno" style={{ top: NumCast(this._overlayAnnoInfo.y), left: NumCast(this._overlayAnnoInfo.x) }}>
                <div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}>
                    {this._overlayAnnoInfo.author + ' ' + Field.toString(this._overlayAnnoInfo.author_date as Field)}
                </div>
            </div>
        );
    }

    getScrollHeight = () => this._scrollHeight;
    showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
    scrollXf = () => (this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._layout_scrollTop)) : this.props.ScreenToLocalTransform());
    overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._freeform_scale, 1));
    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
        if (doc instanceof Doc && property === StyleProp.PointerEvents) {
            if (this.inlineTextAnnotations.includes(doc)) return 'none';
            return 'all';
        }
        return this.props.styleProvider?.(doc, props, property);
    };

    renderAnnotations = (childFilters: () => string[], mixBlendMode?: any, display?: string) => (
        <div
            className="pdfViewerDash-overlay"
            style={{
                mixBlendMode: mixBlendMode,
                display: display,
                pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
            }}>
            <CollectionFreeFormView
                {...this.props}
                NativeWidth={returnZero}
                NativeHeight={returnZero}
                setContentView={emptyFunction} // override setContentView to do nothing
                pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
                childPointerEvents="all" // but freeform children need to get events to allow text editing, etc
                renderDepth={this.props.renderDepth + 1}
                isAnnotationOverlay={true}
                fieldKey={this.props.fieldKey + '_annotations'}
                getScrollHeight={this.getScrollHeight}
                setPreviewCursor={this.setPreviewCursor}
                setBrushViewer={this.setBrushViewer}
                PanelHeight={this.panelHeight}
                PanelWidth={this.panelWidth}
                ScreenToLocalTransform={this.overlayTransform}
                isAnyChildContentActive={returnFalse}
                isAnnotationOverlayScrollable={true}
                dropAction="embed"
                childFilters={childFilters}
                select={emptyFunction}
                bringToFront={emptyFunction}
                styleProvider={this.childStyleProvider}
            />
        </div>
    );
    @computed get overlayTransparentAnnotations() {
        return this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length ? 'none' : undefined);
    }
    @computed get overlayOpaqueAnnotations() {
        return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined);
    }
    @computed get overlayLayer() {
        return (
            <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
                {this.overlayTransparentAnnotations}
                {this.overlayOpaqueAnnotations}
            </div>
        );
    }
    @computed get pdfViewerDiv() {
        return <div className={'pdfViewerDash-text' + (this.props.pointerEvents?.() !== 'none' && this._textSelecting && this.props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
    }
    savedAnnotations = () => this._savedAnnotations;
    render() {
        TraceMobx();
        return (
            <div className="pdfViewer-content">
                <div
                    className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' ? '-interactive' : ''}`}
                    ref={this._mainCont}
                    onScroll={this.onScroll}
                    onWheel={this.onZoomWheel}
                    onPointerDown={this.onPointerDown}
                    onClick={this.onClick}
                    style={{
                        overflowX: NumCast(this.props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
                        height: !this.props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `100%`,
                    }}>
                    {this.pdfViewerDiv}
                    {this.annotationLayer}
                    {this.overlayLayer}
                    {this.overlayInfo}
                    {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null}
                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
                        <MarqueeAnnotator
                            rootDoc={this.props.rootDoc}
                            getPageFromScroll={this.getPageFromScroll}
                            anchorMenuClick={this.props.anchorMenuClick}
                            scrollTop={0}
                            down={this._marqueeing}
                            addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
                            docView={this.props.docViewPath().lastElement()}
                            finishMarquee={this.finishMarquee}
                            savedAnnotations={this.savedAnnotations}
                            selectionText={this.selectionText}
                            annotationLayer={this._annotationLayer.current}
                            mainCont={this._mainCont.current}
                            anchorMenuCrop={this._textSelecting ? undefined : this.crop}
                        />
                    )}
                </div>
            </div>
        );
    }
}
