/* eslint-disable jsx-a11y/no-static-element-interactions */
import similarity from 'compute-cosine-similarity';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ClientUtils, lightOrDark, returnFalse } from '../../../../ClientUtils';
import { intersectRect, numberRange } from '../../../../Utils';
import { Doc, DocListCast, NumListCast, Opt } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkField, InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { RichTextField } from '../../../../fields/RichTextField';
import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
import { GetEffectiveAcl } from '../../../../fields/util';
import { gptGetEmbedding, gptImageLabel } from '../../../apis/gpt/GPT';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { DocUtils } from '../../../documents/DocUtils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { Docs, DocumentOptions } from '../../../documents/Documents';
import { SnappingManager, freeformScrollMode } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { UndoManager, undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { MarqueeViewBounds } from '../../PinFuncs';
import { PreviewCursor } from '../../PreviewCursor';
import { DocumentView } from '../../nodes/DocumentView';
import { OpenWhere } from '../../nodes/OpenWhere';
import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { CollectionCardView } from '../CollectionCardDeckView';
import { SubCollectionViewProps } from '../CollectionSubView';
import { CollectionFreeFormView } from './CollectionFreeFormView';
import { ImageLabelHandler } from './ImageLabelHandler';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
import './MarqueeView.scss';
import { MainView } from '../../MainView';
import { ImageLabelBox, ImageLabelBoxData } from './ImageLabelBox';
import { SearchBox } from '../../search/SearchBox';

interface MarqueeViewProps {
    getContainerTransform: () => Transform;
    getTransform: () => Transform;
    activeDocuments: () => Doc[];
    selectDocuments: (docs: Doc[]) => void;
    addLiveTextDocument: (doc: Doc) => void;
    isSelected: () => boolean;
    panXFieldKey: string;
    panYFieldKey: string;
    trySelectCluster: (addToSel: boolean) => boolean;
    nudge?: (x: number, y: number, nudgeTime?: number) => boolean;
    ungroup?: () => void;
    setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => void;
    slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
}

/**
 * A component that deals with the marquee select in the freeform canvas.
 */
@observer
export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps & MarqueeViewProps> {
    public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) {
        const ps = NumCast(pinDoc._freeform_scale, 1);
        return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps };
    }

    static Instance: MarqueeView;

    constructor(props: any) {
        super(props);
        makeObservable(this);
        MarqueeView.Instance = this;
    }

    private _commandExecuted = false;
    private _selectedDocs: Doc[] = [];
    @observable _lastX: number = 0;
    @observable _lastY: number = 0;
    @observable _downX: number = 0;
    @observable _downY: number = 0;
    @observable _visible: boolean = false; // selection rentangle for marquee selection/free hand lasso is visible
    @observable _labelsVisibile: boolean = false;
    @observable _lassoPts: [number, number][] = [];
    @observable _lassoFreehand: boolean = false;

    @computed get Transform() {
        return this._props.getTransform();
    }
    @computed get Bounds() {
        // nda - ternary argument to transformPoint is returning the lower of the downX/Y and lastX/Y and passing in as args x,y
        const topLeft = this.Transform.transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
        // nda - args to transformDirection is just x and y diff btw downX/Y and lastX/Y
        const size = this.Transform.transformDirection(this._lastX - this._downX, this._lastY - this._downY);
        const bounds: MarqueeViewBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
        return bounds;
    }

    componentDidMount() {
        this._props.setPreviewCursor?.(this.setPreviewCursor);
    }

    @action
    cleanupInteractions = (all: boolean = false, hideMarquee: boolean = true) => {
        if (all) {
            document.removeEventListener('pointerup', this.onPointerUp, true);
            document.removeEventListener('pointermove', this.onPointerMove, true);
        }
        document.removeEventListener('keydown', this.marqueeCommand, true);
        hideMarquee && this.hideMarquee();

        this._lassoPts = [];
    };

    @action
    onKeyDown = (e: KeyboardEvent) => {
        // make textbox and add it to this collection
        // tslint:disable-next-line:prefer-const
        const cm = ContextMenu.Instance;
        const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
        if (e.key === '?') {
            cm.setDefaultItem('?', (str: string) =>
                this._props.addDocTab(Docs.Create.WebDocument(`https://wikipedia.org/wiki/${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: `wiki:${str}`, data_useCors: true }), OpenWhere.addRight)
            );
            cm.displayMenu(this._downX, this._downY, undefined, true);
            e.stopPropagation();
        } else if (e.key === 'u' && this._props.ungroup) {
            e.stopPropagation();
            this._props.ungroup();
        } else if (e.key === ':') {
            DocUtils.addDocumentCreatorMenuItems(this._props.addLiveTextDocument, this._props.addDocument || returnFalse, x, y);

            cm.displayMenu(this._downX, this._downY, undefined, true);
            e.stopPropagation();
        } else if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
            e.preventDefault();
            this._props.selectDocuments(this._props.activeDocuments());
            e.stopPropagation();
        } else if (e.key === 'q' && e.ctrlKey) {
            e.preventDefault();
            (async () => {
                const text: string = await navigator.clipboard.readText();
                const ns = text.split('\n').filter(t => t.trim() !== '\r' && t.trim() !== '');
                for (let i = 0; i < ns.length - 1; i++) {
                    while (
                        !(ns[i].trim() === '' || ns[i].endsWith('-\r') || ns[i].endsWith('-') || ns[i].endsWith(';\r') || ns[i].endsWith(';') || ns[i].endsWith('.\r') || ns[i].endsWith('.') || ns[i].endsWith(':\r') || ns[i].endsWith(':')) &&
                        i < ns.length - 1
                    ) {
                        const sub = ns[i].endsWith('\r') ? 1 : 0;
                        const br = ns[i + 1].trim() === '';
                        ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft());
                        if (br) break;
                    }
                }
                let ypos = y;
                ns.forEach(line => {
                    const indent = line.search(/\S|$/);
                    const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line });
                    this._props.addDocument?.(newBox);
                    ypos += 40 * this.Transform.Scale;
                });
            })();
            e.stopPropagation();
        } else if (e.key === 'b' && e.ctrlKey) {
            document.body.focus(); // so that we can access the clipboard without an error
            setTimeout(() =>
                pasteImageBitmap((data: any, error: any) => {
                    error && console.log(error);
                    data &&
                        ClientUtils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
                            this._props.Document['thumb-frozen'] = new ImageField(returnedfilename);
                        });
                })
            );
        } /* else if (e.key === 's' && e.ctrlKey) {
            e.preventDefault();
            const slide = DocUtils.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!;
            slide.x = x;
            slide.y = y;
            Doc.SetSelectOnLoad(slide);
            TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
            this._props.addDocument?.(slide);
            e.stopPropagation();
        } */ else if (e.key === 'p' && e.ctrlKey) {
            e.preventDefault();
            (async () => {
                const text: string = await navigator.clipboard.readText();
                const ns = text.split('\n').filter(t => t.trim() !== '\r' && t.trim() !== '');
                this.pasteTable(ns, x, y);
            })();
            e.stopPropagation();
        } else if (!e.ctrlKey && !e.metaKey && DocumentView.Selected().length < 2) {
            FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : '';
            FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
            this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100));
            e.stopPropagation();
        }
    };
    // heuristically converts pasted text into a table.
    // assumes each entry is separated by a tab
    // skips all rows until it gets to a row with more than one entry
    // assumes that 1st row has header entry for each column
    // assumes subsequent rows have entries for each column header OR
    //         any row that has only one column is a section header-- this header is then added as a column to subsequent rows until the next header
    // assumes each cell is a string or a number
    pasteTable(ns: string[], x: number, y: number) {
        const csvRows = [];
        const headers = ns[0].split('\t');
        csvRows.push(headers.join(','));
        ns[0] = '';
        const eachCell = ns.join('\t').split('\t');
        let eachRow = [];
        for (let i = 1; i < eachCell.length; i++) {
            eachRow.push(eachCell[i].replace(/,/g, ''));
            if (i % headers.length === 0) {
                csvRows.push(eachRow);
                eachRow = [];
            }
        }

        const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
        const options = { x: x, y: y, title: 'droppedTable', _width: 300, _height: 100, type: 'text/csv' };
        const file = new File([blob], 'droppedTable', options);
        const loading = Docs.Create.LoadingDocument(file, options);
        DocUtils.uploadFileToDoc(file, {}, loading);
        this._props.addDocument?.(loading);
    }

    @action
    onPointerDown = (e: React.PointerEvent): void => {
        this._downX = this._lastX = e.clientX;
        this._downY = this._lastY = e.clientY;

        const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
        // allow marquee if right drag/meta drag, or pan mode
        if (e.button === 2 || e.metaKey || (this._props.isContentActive() && scrollMode === freeformScrollMode.Pan && Doc.ActiveTool === InkTool.None)) {
            this.setPreviewCursor(e.clientX, e.clientY, true, false, this._props.Document);
            e.preventDefault();
        } else PreviewCursor.Instance.Visible = false;
    };

    @action
    onPointerMove = (e: PointerEvent): void => {
        this._lastX = e.pageX;
        this._lastY = e.pageY;
        this._lassoPts.push([e.clientX, e.clientY]);
        if (!e.cancelBubble) {
            if (!ClientUtils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) {
                if (!this._commandExecuted) {
                    this.showMarquee();
                }
                e.stopPropagation();
                e.preventDefault();
            }
        } else {
            this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this
        }
        e.altKey && e.preventDefault();
    };

    @action
    onPointerUp = (e: PointerEvent): void => {
        if (this._visible) {
            const mselect = this.marqueeSelect();
            if (!e.shiftKey) {
                DocumentView.DeselectAll(mselect.length ? undefined : this._props.Document);
            }
            const docs = mselect.length ? mselect : [this._props.Document];
            this._props.selectDocuments(docs);
        }
        const hideMarquee = () => {
            this.hideMarquee();
            MarqueeOptionsMenu.Instance.fadeOut(true);
            document.removeEventListener('pointerdown', hideMarquee, true);
            document.removeEventListener('wheel', hideMarquee, true);
        };
        if (!this._commandExecuted && Math.abs(this.Bounds.height * this.Bounds.width) > 100) {
            MarqueeOptionsMenu.Instance.createCollection = this.collection;
            MarqueeOptionsMenu.Instance.delete = this.delete;
            MarqueeOptionsMenu.Instance.summarize = this.summary;
            MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
            MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
            MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
            MarqueeOptionsMenu.Instance.pinWithView = this.pinWithView;
            MarqueeOptionsMenu.Instance.classifyImages = this.classifyImages;
            MarqueeOptionsMenu.Instance.groupImages = this.groupImages;
            document.addEventListener('pointerdown', hideMarquee, true);
            document.addEventListener('wheel', hideMarquee, true);
        } else {
            this.hideMarquee();
        }
        this.cleanupInteractions(true, this._commandExecuted);

        e.altKey && e.preventDefault();
    };

    clearSelection() {
        if (window.getSelection) {
            window.getSelection()?.removeAllRanges();
        } else if (document.getSelection()) {
            document.getSelection()?.empty();
        }
    }

    setPreviewCursor = action((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => {
        if (hide) {
            this._downX = this._lastX = x;
            this._downY = this._lastY = y;
            this._commandExecuted = false;
            PreviewCursor.Instance.Visible = false;
            PreviewCursor.Instance.Doc = undefined;
        } else if (drag) {
            this._downX = this._lastX = x;
            this._downY = this._lastY = y;
            this._commandExecuted = false;
            PreviewCursor.Instance.Visible = false;
            PreviewCursor.Instance.Doc = undefined;
            this.cleanupInteractions(true);
            document.addEventListener('pointermove', this.onPointerMove, true);
            document.addEventListener('pointerup', this.onPointerUp, true);
            document.addEventListener('keydown', this.marqueeCommand, true);
        } else {
            this._downX = x;
            this._downY = y;
            const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]);
            if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
                PreviewCursor.Instance.Doc = doc;
                PreviewCursor.Show(x, y, this.onKeyDown, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
            }
            this.clearSelection();
        }
    });

    @action
    onClick = (e: React.MouseEvent): void => {
        if (this._props.pointerEvents?.() === 'none') return;
        if (ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
            if (Doc.ActiveTool === InkTool.None) {
                if (!this._props.trySelectCluster(e.shiftKey)) {
                    !SnappingManager.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
                } else e.stopPropagation();
            }
            // let the DocumentView stopPropagation of this event when it selects this document
        } else {
            // why do we get a click event when the cursor have moved a big distance?
            // let's cut it off here so no one else has to deal with it.
            e.stopPropagation();
        }
    };

    @action
    showMarquee = () => {
        this._visible = true;
    };
    @action
    hideMarquee = () => {
        this._visible = false;
    };

    @undoBatch
    delete = action((e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
        const selected = this.marqueeSelect(false);
        DocumentView.DeselectAll();
        selected.forEach(doc => {
            hide ? (doc.hidden = true) : this._props.removeDocument?.(doc);
        });

        this.cleanupInteractions(false);
        MarqueeOptionsMenu.Instance.fadeOut(true);
        this.hideMarquee();
    });

    getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt<boolean>) => {
        const newCollection = creator
            ? creator(selected, { title: 'nested stack' })
            : ((doc: Doc) => {
                  const docData = doc[DocData];
                  docData.data = new List<Doc>(selected);
                  docData.isGroup = makeGroup;
                  docData.title = makeGroup ? 'grouping' : 'nested freeform';
                  doc._freeform_panX = doc._freeform_panY = 0;
                  return doc;
              })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
        newCollection.isSystem = undefined;
        newCollection._width = this.Bounds.width;
        newCollection._height = this.Bounds.height;
        newCollection._dragWhenActive = makeGroup;
        newCollection.x = this.Bounds.left;
        newCollection.y = this.Bounds.top;
        newCollection.layout_fitWidth = true;
        selected.forEach(d => Doc.SetContainer(d, newCollection));
        this.hideMarquee();
        return newCollection;
    });

    @undoBatch
    pileup = action(() => {
        const selected = this.marqueeSelect(false);
        DocumentView.DeselectAll();
        selected.forEach(d => this._props.removeDocument?.(d));
        const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!;
        this._props.addDocument?.(newCollection);
        this._props.selectDocuments([newCollection]);
        MarqueeOptionsMenu.Instance.fadeOut(true);
        this.hideMarquee();
    });

    /**
     * This triggers the DocumentView.PinDoc method which is the universal method
     * used to pin documents to the currently active presentation trail.
     *
     * This one is unique in that it includes the bounds associated with marquee view.
     */
    @undoBatch
    pinWithView = action(() => {
        this._props.pinToPres(this._props.Document, { pinViewport: this.Bounds });
        MarqueeOptionsMenu.Instance.fadeOut(true);
        this.hideMarquee();
    });

    @undoBatch
    collection = action((e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
        const selected = selection ?? this.marqueeSelect(false);
        const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined);
        if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) {
            this._props.removeDocument?.(selected);
        }

        const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === 't' ? Docs.Create.StackingDocument : undefined, group);
        newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
        newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2;
        newCollection._currentFrame = activeFrame;
        this._props.addDocument?.(newCollection);
        this._props.selectDocuments([newCollection]);
        MarqueeOptionsMenu.Instance.fadeOut(true);
        this.hideMarquee();
    });

    /**
     * Classifies images and assigns the labels as document fields.
     */
    @undoBatch
    classifyImages = action(async (e: React.MouseEvent | undefined) => {
        const groupButton = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImageGrouper);
        if (groupButton) {
            this._selectedDocs = this.marqueeSelect(false, DocumentType.IMG);
            ImageLabelBoxData.Instance.setData(this._selectedDocs);
            MainView.Instance.expandFlyout(groupButton);
        }
    });

    /**
     * Groups images to most similar labels.
     */
    @undoBatch
    groupImages = action(async () => {
        const labelGroups: string[] = ImageLabelBoxData.Instance._labelGroups;
        const labelToCollection: Map<string, Doc> = new Map();
        const selectedImages = ImageLabelBoxData.Instance._docs;

        // Create new collections associated with each label and get the embeddings for the labels.
        let x_offset = 0;
        let y_offset = 0;
        let row_count = 0;
        for (const label of labelGroups) {
            const newCollection = this.getCollection([], undefined, false);
            newCollection._width = 900;
            newCollection._height = 900;
            newCollection._x = this.Bounds.left;
            newCollection._y = this.Bounds.top;
            newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
            newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2;
            newCollection._x = (newCollection._x as number) + x_offset;
            newCollection._y = (newCollection._y as number) + y_offset;
            x_offset += (newCollection._width as number) + 40;
            row_count += 1;
            if (row_count == 3) {
                y_offset += (newCollection._height as number) + 40;
                x_offset = 0;
                row_count = 0;
            }
            labelToCollection.set(label, newCollection);
            this._props.addDocument?.(newCollection);
        }

        for (const doc of selectedImages) {
            if (doc[DocData].data_label) {
                Doc.AddDocToList(labelToCollection.get(doc[DocData].data_label as string)!, undefined, doc);
                this._props.removeDocument?.(doc);
            }
        }

        //this._props.Document._type_collection = CollectionViewType.Time; // Change the collection view to a Time view.
        //this._props.Document.pivotField = 'data_label'; // Sets the pivot to be the 'data_label'.
    });

    @undoBatch
    syntaxHighlight = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
        const selected = this.marqueeSelect(false);
        if (e instanceof KeyboardEvent ? e.key === 'i' : true) {
            const inks = selected.filter(s => s.type === DocumentType.INK);
            const setDocs = selected.filter(s => s.type === DocumentType.RTF && s.color);
            const sets = setDocs.map(sd => Cast(sd.data, RichTextField)?.Text as string);
            const colors = setDocs.map(sd => FieldValue(sd.color) as string);
            const wordToColor = new Map<string, string>();
            sets.forEach((st: string, i: number) => st.split(',').forEach(word => wordToColor.set(word, colors[i])));
            const strokes: InkData[] = [];
            inks.filter(i => Cast(i.data, InkField)).forEach(i => {
                const d = Cast(i.data, InkField, null);
                const left = Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
                const top = Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
                strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top })));
            });
            CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {
                // const wordResults = results.filter((r: any) => r.category === "inkWord");
                // for (const word of wordResults) {
                //     const indices: number[] = word.strokeIds;
                //     indices.forEach(i => {
                //         if (wordToColor.has(word.recognizedText.toLowerCase())) {
                //             inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
                //         }
                //         else {
                //             for (const alt of word.alternates) {
                //                 if (wordToColor.has(alt.recognizedString.toLowerCase())) {
                //                     inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
                //                     break;
                //                 }
                //             }
                //         }
                //     })
                // }
                // const wordResults = results.filter((r: any) => r.category === "inkWord");
                // for (const word of wordResults) {
                //     const indices: number[] = word.strokeIds;
                //     indices.forEach(i => {
                //         const otherInks: Doc[] = [];
                //         indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2]));
                //         inks[i].relatedInks = new List<Doc>(otherInks);
                //         const uniqueColors: string[] = [];
                //         Array.from(wordToColor.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
                //         inks[i].alternativeColors = new List<string>(uniqueColors);
                //         if (wordToColor.has(word.recognizedText.toLowerCase())) {
                //             inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
                //         }
                //         else if (word.alternates) {
                //             for (const alt of word.alternates) {
                //                 if (wordToColor.has(alt.recognizedString.toLowerCase())) {
                //                     inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
                //                     break;
                //                 }
                //             }
                //         }
                //     });
                // }
                const lines = results.filter((r: any) => r.category === 'line');
                const text = lines.map((l: any) => l.recognizedText).join('\r\n');
                this._props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
            });
        }
    });

    @undoBatch
    summary = action(() => {
        const selected = this.marqueeSelect(false).map(d => {
            this._props.removeDocument?.(d);
            d.x = NumCast(d.x) - this.Bounds.left;
            d.y = NumCast(d.y) - this.Bounds.top;
            return d;
        });
        const summary = Docs.Create.TextDocument('', {
            backgroundColor: '#e2ad32',
            x: this.Bounds.left,
            y: this.Bounds.top,
            followLinkToggle: true,
            _width: 200,
            _height: 200,
            _layout_showSidebar: true,
            title: 'overview',
        });
        const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
        DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });

        portal.hidden = true;
        this._props.addDocument?.(portal);
        this._props.addLiveTextDocument(summary);
        MarqueeOptionsMenu.Instance.fadeOut(true);
    });

    @action
    marqueeCommand = (e: KeyboardEvent) => {
        if (this._commandExecuted || (e as any).propagationIsStopped) {
            return;
        }
        if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd' || e.key === 'h') {
            this._commandExecuted = true;
            e.stopPropagation();
            (e as any).propagationIsStopped = true;
            this.delete(e, e.key === 'h');
            e.stopPropagation();
        }
        if ('ctsSpg'.indexOf(e.key) !== -1) {
            this._commandExecuted = true;
            e.stopPropagation();
            e.preventDefault();
            (e as any).propagationIsStopped = true;
            if (e.key === 'g') this.collection(e, true);
            if (e.key === 'c' || e.key === 't') this.collection(e);
            if (e.key === 's' || e.key === 'S') this.summary();
            if (e.key === 'p') this.pileup();
            this.cleanupInteractions(false);
        }
        if (e.key === 'r' || e.key === ' ') {
            this._commandExecuted = true;
            e.stopPropagation();
            e.preventDefault();
            this._lassoFreehand = !this._lassoFreehand;
        }
    };

    touchesLine(r1: { left: number; top: number; width: number; height: number }) {
        // eslint-disable-next-line no-restricted-syntax
        for (const lassoPt of this._lassoPts) {
            const topLeft = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
            if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width && r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) {
                return true;
            }
        }
        return false;
    }

    boundingShape(r1: { left: number; top: number; width: number; height: number }) {
        const xs = this._lassoPts.map(pair => pair[0]);
        const ys = this._lassoPts.map(pair => pair[1]);
        const tl = this.Transform.transformPoint(Math.min(...xs), Math.min(...ys));
        const br = this.Transform.transformPoint(Math.max(...xs), Math.max(...ys));

        if (r1.left > tl[0] && r1.top > tl[1] && r1.left + r1.width < br[0] && r1.top + r1.height < br[1]) {
            let hasTop = false;
            let hasLeft = false;
            let hasBottom = false;
            let hasRight = false;
            // eslint-disable-next-line no-restricted-syntax
            for (const lassoPt of this._lassoPts) {
                const truePoint = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
                hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
                hasTop = hasTop || (truePoint[1] > tl[1] && truePoint[1] < r1.top && truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width);
                hasRight = hasRight || (truePoint[0] < br[0] && truePoint[0] > r1.left + r1.width && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
                hasBottom = hasBottom || (truePoint[1] < br[1] && truePoint[1] > r1.top + r1.height && truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width);
                if (hasTop && hasLeft && hasBottom && hasRight) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * When this is called, returns the list of documents that have been selected by the marquee box.
     */
    marqueeSelect(selectBackgrounds: boolean = false, docType: DocumentType | undefined = undefined) {
        const selection: Doc[] = [];
        const selectFunc = (doc: Doc) => {
            const layoutDoc = Doc.Layout(doc);
            const bounds = { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(layoutDoc._width), height: NumCast(layoutDoc._height) };
            if (!this._lassoFreehand) {
                intersectRect(bounds, this.Bounds) && selection.push(doc);
            } else {
                (this.touchesLine(bounds) || this.boundingShape(bounds)) && selection.push(doc);
            }
        };
        if (docType) {
            this._props
                .activeDocuments()
                .filter(doc => !doc.z && !doc._lockedPosition && doc.type === docType)
                .map(selectFunc);
        } else {
            this._props
                .activeDocuments()
                .filter(doc => !doc.z && !doc._lockedPosition)
                .map(selectFunc);
        }
        if (!selection.length && selectBackgrounds)
            this._props
                .activeDocuments()
                .filter(doc => doc.z === undefined)
                .map(selectFunc);
        if (!selection.length)
            this._props
                .activeDocuments()
                .filter(doc => doc.z !== undefined)
                .map(selectFunc);
        return selection;
    }

    @computed get marqueeDiv() {
        const cpt = this._lassoFreehand || !this._visible ? [0, 0] : [this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY];
        const p = this._props.getContainerTransform().transformPoint(cpt[0], cpt[1]);
        const v = this._lassoFreehand ? [0, 0] : this._props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
        return (
            <div
                className="marquee"
                style={{
                    transform: `translate(${p[0]}px, ${p[1]}px)`,
                    width: Math.abs(v[0]),
                    height: Math.abs(v[1]),
                    color: lightOrDark(this._props.Document?.backgroundColor ?? 'white'),
                    borderColor: lightOrDark(this._props.Document?.backgroundColor ?? 'white'),
                    zIndex: 2000,
                }}>
                {' '}
                {this._lassoFreehand ? (
                    <svg height={2000} width={2000}>
                        <polyline //
                            points={this._lassoPts.reduce((s, pt) => s + pt[0] + ',' + pt[1] + ' ', '')}
                            fill="none"
                            stroke={lightOrDark(this._props.Document?.backgroundColor ?? 'white')}
                            strokeWidth="1"
                            strokeDasharray="3"
                        />
                    </svg>
                ) : (
                    <span className="marquee-legend" />
                )}
            </div>
        );
    }
    MarqueeRef: HTMLDivElement | null = null;

    /**
     * This is called for every drag movement when a document is dragged over this collection.
     * If the document is dragged within 25 pixels of the edge of the collection and paused, this will
     * auto scroll the collection so that it can be dragged farther (unless auto panning has been disabled)
     */
    @action
    onDragMovePause = (e: CustomEvent<React.DragEvent>) => {
        if ((e as any).handlePan || this._props.isAnnotationOverlay) return;
        (e as any).handlePan = true;

        const bounds = this.MarqueeRef?.getBoundingClientRect();
        if (!this._props.Document._freeform_noAutoPan && !this._props.renderDepth && bounds) {
            const dragX = e.detail.clientX;
            const dragY = e.detail.clientY;

            const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
            const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
            if (deltaX !== 0 || deltaY !== 0) {
                this._props.Document[this._props.panYFieldKey] = NumCast(this._props.Document[this._props.panYFieldKey]) + deltaY / 2;
                this._props.Document[this._props.panXFieldKey] = NumCast(this._props.Document[this._props.panXFieldKey]) + deltaX / 2;
            }
        }
        e.stopPropagation();
    };
    render() {
        return (
            // eslint-disable-next-line jsx-a11y/click-events-have-key-events
            <div
                className="marqueeView"
                ref={r => {
                    r?.addEventListener('dashDragMovePause', this.onDragMovePause as any);
                    this.MarqueeRef = r;
                }}
                style={{
                    overflow: StrCast(this._props.Document._overflow),
                    cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',
                }}
                onDragOver={e => e.preventDefault()}
                onScroll={e => {
                    e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0;
                }}
                onClick={this.onClick}
                onPointerDown={this.onPointerDown}>
                {this._visible ? this.marqueeDiv : null}
                {this.props.children}
            </div>
        );
    }
}
