import { action, observable, ObservableMap, runInAction } from "mobx";
import { observer } from "mobx-react";
import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt, AclSelfEdit } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { List } from "../../fields/List";
import { NumCast } from "../../fields/Types";
import { GetEffectiveAcl } from "../../fields/util";
import { unimplementedFunction, Utils } from "../../Utils";
import { Docs } from "../documents/Documents";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { DragManager } from "../util/DragManager";
import { undoBatch } from "../util/UndoManager";
import "./MarqueeAnnotator.scss";
import { DocumentView } from "./nodes/DocumentView";
import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
import { AnchorMenu } from "./pdf/AnchorMenu";
import React = require("react");
const _global = (window /* browser */ || global /* node */) as any;

export interface MarqueeAnnotatorProps {
    rootDoc: Doc;
    down: number[];
    iframe?: () => undefined | HTMLIFrameElement;
    scrollTop: number;
    scaling?: () => number;
    iframeScaling?: () => number;
    containerOffset?: () => number[];
    mainCont: HTMLDivElement;
    docView: DocumentView;
    savedAnnotations: () => ObservableMap<number, HTMLDivElement[]>;
    annotationLayer: HTMLDivElement;
    addDocument: (doc: Doc) => boolean;
    getPageFromScroll?: (top: number) => number;
    finishMarquee: (x?: number, y?: number, PointerEvent?: PointerEvent) => void;
    anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
    anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined;
}
@observer
export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
    private _startX: number = 0;
    private _startY: number = 0;
    @observable private _left: number = 0;
    @observable private _top: number = 0;
    @observable private _width: number = 0;
    @observable private _height: number = 0;

    @action
    static clearAnnotations(savedAnnotations: ObservableMap<number, HTMLDivElement[]>) {
        AnchorMenu.Instance.Status = "marquee";
        AnchorMenu.Instance.fadeOut(true);
        // clear out old marquees and initialize menu for new selection
        Array.from(savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
        savedAnnotations.clear();
    }

    @action componentDidMount() {
        // set marquee x and y positions to the spatially transformed position
        const boundingRect = this.props.mainCont.getBoundingClientRect();
        this._startX = this._left = (this.props.down[0] - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width);
        this._startY = this._top = (this.props.down[1] - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop;
        this._height = this._width = 0;

        const doc = (this.props.iframe?.()?.contentDocument ?? document);
        doc.addEventListener("pointermove", this.onSelectMove);
        doc.addEventListener("pointerup", this.onSelectEnd);

        AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight("rgba(173, 216, 230, 0.75)", true), true);
        AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true));
        AnchorMenu.Instance.Highlight = this.highlight;
        AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight("rgba(173, 216, 230, 0.75)", true, savedAnnotations);
        AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor;

        /**
         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.  
         * It also initiates a Drag/Drop interaction to place the text annotation.
         */
        AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => {
            e.preventDefault();
            e.stopPropagation();
            const sourceAnchorCreator = () => {
                const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color
                annoDoc && this.props.addDocument(annoDoc);
                return annoDoc;
            };
            const targetCreator = (annotationOn: Doc | undefined) => {
                const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, "yellow");
                FormattedTextBox.SelectOnLoad = target[Id];
                return target;
            };
            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                dragComplete: e => {
                    if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
                        e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
                    }
                }
            });
        });
        /**
         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.  
         * It also initiates a Drag/Drop interaction to place the text annotation.
         */
        AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop ? unimplementedFunction : action((e: PointerEvent, ele: HTMLElement) => {
            e.preventDefault();
            e.stopPropagation();
            var cropRegion: Doc | undefined;
            const sourceAnchorCreator = () => {
                cropRegion = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color
                cropRegion && this.props.addDocument(cropRegion);
                return cropRegion;
            };
            const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                dragComplete: e => {
                    if (!e.aborted && e.linkDocument) {
                        Doc.GetProto(e.linkDocument).linkRelationship = "cropped image";
                        Doc.GetProto(e.linkDocument).title = "crop: " + this.props.docView.rootDoc.title;
                    }
                }
            });
        });
    }
    componentWillUnmount() {
        const doc = (this.props.iframe?.()?.contentDocument ?? document);
        doc.removeEventListener("pointermove", this.onSelectMove);
        doc.removeEventListener("pointerup", this.onSelectEnd);
    }

    @undoBatch
    @action
    makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
        const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
        if (savedAnnoMap.size === 0) return undefined;
        const savedAnnos = Array.from(savedAnnoMap.values())[0];
        if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) {
            const scale = (this.props.scaling?.() || 1);
            const anno = savedAnnos[0];
            const containerOffset = this.props.containerOffset?.() || [0, 0];
            const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title });
            marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || "0") - containerOffset[0]) / scale/ NumCast(this.props.docView.props.Document._viewScale,1);
            marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || "0") - containerOffset[1]) / scale/ NumCast(this.props.docView.props.Document._viewScale,1) + NumCast(this.props.scrollTop);
            marqueeAnno._height = parseInt(anno.style.height || "0") / scale/ NumCast(this.props.docView.props.Document._viewScale,1);
            marqueeAnno._width = parseInt(anno.style.width || "0") / scale/ NumCast(this.props.docView.props.Document._viewScale,1);
            anno.remove();
            savedAnnoMap.clear();
            return marqueeAnno;
        }

        const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: "transparent", title: "Selection on " + this.props.rootDoc.title });
        let minX = Number.MAX_VALUE;
        let maxX = -Number.MAX_VALUE;
        let minY = Number.MAX_VALUE;
        let maxY = -Number.MIN_VALUE;
        const annoDocs: Doc[] = [];
        savedAnnoMap.forEach((value: HTMLDivElement[], key: number) => value.map(anno => {
            const textRegion = new Doc();
            textRegion.x = parseInt(anno.style.left ?? "0");
            textRegion.y = parseInt(anno.style.top ?? "0");
            textRegion._height = parseInt(anno.style.height ?? "0");
            textRegion._width = parseInt(anno.style.width ?? "0");
            textRegion.annoTextRegion = textRegionAnno;
            textRegion.backgroundColor = color;
            annoDocs.push(textRegion);
            anno.remove();
            minY = Math.min(NumCast(textRegion.y), minY);
            minX = Math.min(NumCast(textRegion.x), minX);
            maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY);
            maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX);
        }));

        const textRegionAnnoProto = Doc.GetProto(textRegionAnno);
        textRegionAnnoProto.y = Math.max(minY, 0);
        textRegionAnnoProto.x = Math.max(minX, 0);
        textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
        textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0);
        // mainAnnoDocProto.text = this._selectionText;
        textRegionAnnoProto.textInlineAnnotations = new List<Doc>(annoDocs);
        savedAnnoMap.clear();
        return textRegionAnno;
    }
    @action
    highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => {
        // creates annotation documents for current highlights
        const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]);
        const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
        !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
        return annotationDoc as Doc ?? undefined;
    }

    public static previewNewAnnotation = action((savedAnnotations: ObservableMap<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => {
        if (div.style.top) {
            div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString();
        }
        annotationLayer.append(div);
        div.style.backgroundColor = "#ACCEF7";
        div.style.opacity = "0.5";
        const savedPage = savedAnnotations.get(page);
        if (savedPage) {
            savedPage.push(div);
            savedAnnotations.set(page, savedPage);
        }
        else {
            savedAnnotations.set(page, [div]);
        }
    });

    @action
    onSelectMove = (e: PointerEvent) => {
        // transform positions and find the width and height to set the marquee to
        const boundingRect = (this.props.iframe?.()?.contentDocument?.body || this.props.mainCont).getBoundingClientRect();
        const mainRect = this.props.mainCont.getBoundingClientRect();
        const cliX = e.clientX * (this.props.iframeScaling?.() || 1) - boundingRect.left;
        const cliY = e.clientY * (this.props.iframeScaling?.() || 1) - boundingRect.top;
        this._width = (cliX * (this.props.mainCont.offsetWidth / mainRect.width)) - this._startX;
        this._height = (cliY * (this.props.mainCont.offsetHeight / mainRect.height)) - this._startY + this.props.mainCont.scrollTop;
        this._left = Math.min(this._startX, this._startX + this._width);
        this._top = Math.min(this._startY, this._startY + this._height);
        this._width = Math.abs(this._width);
        this._height = Math.abs(this._height);
        e.stopPropagation();
    }

    onSelectEnd = (e: PointerEvent) => {
        const mainRect = this.props.mainCont.getBoundingClientRect();
        const cliX = e.clientX * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.left : 0);
        const cliY = e.clientY * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.top : 0);
        if (this._width > 10 || this._height > 10) {  // configure and show the annotation/link menu if a the drag region is big enough
            const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox");
            if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though).
                const copy = document.createElement("div");
                ["border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]);
                const bounds = (marquees[0] as HTMLDivElement).getBoundingClientRect();
                const uitls = Utils.GetScreenTransform(marquees[0] as HTMLDivElement);
                const rbounds = { top: uitls.translateY, left: uitls.translateX, width: (bounds.right - bounds.left), height: (bounds.bottom - bounds.top) };
                const otls = Utils.GetScreenTransform(this.props.annotationLayer);
                const fbounds = { top: (rbounds.top - otls.translateY) / otls.scale, left: (rbounds.left - otls.translateX) / otls.scale, width: rbounds.width / otls.scale, height: rbounds.height / otls.scale };
                copy.style.top = fbounds.top.toString() + "px";
                copy.style.left = fbounds.left.toString() + "px";
                copy.style.width = fbounds.width.toString() + "px";
                copy.style.height = fbounds.height.toString() + "px";
                copy.className = "marqueeAnnotator-annotationBox";
                (copy as any).marqueeing = true;
                MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
            }

            AnchorMenu.Instance.jumpTo(cliX, cliY);

            if (AnchorMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
                this.highlight("rgba(245, 230, 95, 0.75)", false);  // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color)
            }
            this.props.finishMarquee(undefined, undefined, e);
        } else {
            runInAction(() => this._width = this._height = 0);
            this.props.finishMarquee(cliX, cliY, e);
        }
    }

    render() {
        return <div className="marqueeAnnotator-dragBox"
            style={{
                left: `${this._left}px`, top: `${this._top}px`,
                width: `${this._width}px`, height: `${this._height}px`,
                border: `${this._width === 0 ? "" : "2px dashed black"}`,
                opacity: 0.2
            }}>
        </div>;
    }
}
