import * as fitCurve from 'fit-curve';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
import { emptyFunction } from '../../Utils';
import { Doc, Opt, returnEmptyDoclist } from '../../fields/Doc';
import { InkData, InkField, InkTool } from '../../fields/InkField';
import { NumCast } from '../../fields/Types';
import { Gestures } from '../../pen-gestures/GestureTypes';
import { GestureUtils } from '../../pen-gestures/GestureUtils';
import { Result } from '../../pen-gestures/ndollar';
import { DocumentType } from '../documents/DocumentTypes';
import { Docs } from '../documents/Documents';
import { InteractionUtils } from '../util/InteractionUtils';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { Transform } from '../util/Transform';
import './GestureOverlay.scss';
import { InkingStroke } from './InkingStroke';
import { ObservableReactComponent } from './ObservableReactComponent';
import { returnEmptyDocViewList } from './StyleProvider';
import { CollectionFreeFormView } from './collections/collectionFreeForm';
import {
    ActiveArrowEnd,
    ActiveArrowScale,
    ActiveArrowStart,
    ActiveDash,
    ActiveFillColor,
    ActiveInkBezierApprox,
    ActiveInkColor,
    ActiveInkWidth,
    DocumentView,
    SetActiveArrowStart,
    SetActiveDash,
    SetActiveFillColor,
    SetActiveInkColor,
    SetActiveInkWidth,
} from './nodes/DocumentView';
export enum ToolglassTools {
    InkToText = 'inktotext',
    IgnoreGesture = 'ignoregesture',
    RadialMenu = 'radialmenu',
    None = 'none',
}
interface GestureOverlayProps {
    isActive: boolean;
}
@observer
/**
 * class for gestures. will determine if what the user drew is a gesture, and will transform the ink stroke into the shape the user
 * drew or perform the gesture's action
 */
export class GestureOverlay extends ObservableReactComponent<React.PropsWithChildren<GestureOverlayProps>> {
    static Instance: GestureOverlay;
    static Instances: GestureOverlay[] = [];

    @observable public InkShape: Opt<Gestures> = undefined;
    @observable public SavedColor?: string = undefined;
    @observable public SavedWidth?: number = undefined;
    @observable public Tool: ToolglassTools = ToolglassTools.None;
    @observable public KeepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode

    @observable private _thumbX?: number = undefined;
    @observable private _thumbY?: number = undefined;
    @observable private _pointerY?: number = undefined;
    @observable private _points: { X: number; Y: number }[] = [];
    @observable private _strokes: InkData[] = [];
    @observable private _palette?: JSX.Element = undefined;
    @observable private _clipboardDoc?: JSX.Element = undefined;
    @observable private _possibilities: JSX.Element[] = [];

    @computed private get height(): number {
        return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100);
    }
    @computed private get showBounds() {
        return this.Tool !== ToolglassTools.None;
    }

    private _overlayRef = React.createRef<HTMLDivElement>();
    private _d1: Doc | undefined;
    private _inkToTextDoc: Doc | undefined;
    private thumbIdentifier?: number;
    private pointerIdentifier?: number;

    constructor(props: GestureOverlayProps) {
        super(props);
        makeObservable(this);
        GestureOverlay.Instances.push(this);
    }

    componentWillUnmount() {
        GestureOverlay.Instances.splice(GestureOverlay.Instances.indexOf(this), 1);
        GestureOverlay.Instance = GestureOverlay.Instances.lastElement();
    }
    componentDidMount() {
        GestureOverlay.Instance = this;
    }

    @action
    onPointerDown = (e: React.PointerEvent) => {
        if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) {
            if ([InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
                this._points.push({ X: e.clientX, Y: e.clientY });
                setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
            }
        }
    };

    @action
    onPointerMove = (e: PointerEvent) => {
        this._points.push({ X: e.clientX, Y: e.clientY });

        if (this._points.length > 1) {
            const initialPoint = this._points[0];
            const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
            const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
            if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
                switch (this.Tool) {
                    case ToolglassTools.RadialMenu: return true;
                    default: 
                } // prettier-ignore
            }
        }
        return false;
    };

    @action primCreated() {
        if (!this.KeepPrimitiveMode) {
            this.InkShape = undefined;
            // get out of ink mode after each stroke=
            // if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
            Doc.ActiveTool = InkTool.None;
            // SetActiveArrowStart('none');
            // SetActiveArrowEnd('none');
        }
    }
    /**
     * this method returns if what the user drew is a scribble. if it is, it will determine what documents need
     * to be deleted and then it will delete them.
     * @returns
     */
    isScribble() {
        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
        const cuspArray = this.getCusps();
        const cuspBooleanArray: boolean[] = [];
        const docsToDelete: Doc[] = [];
        const childDocs = (ffView?.ComponentView as CollectionFreeFormView).childDocs;
        childDocs.filter(doc => doc.type === 'ink').map(doc => DocumentView.getDocumentView(doc, DocumentView.getDocumentView(doc)));
        if ((ffView?.ComponentView as CollectionFreeFormView).childDocs) {
            //how many cusps the scribble hsa
            if (cuspArray.length > 4) {
                for (let i = 0; i < cuspArray.length - 2; i++) {
                    let hasDocInTriangle = false;
                    for (const doc of childDocs) {
                        const point1 = cuspArray[i];
                        const point2 = cuspArray[i + 1];
                        const point3 = cuspArray[i + 2];
                        const triangleObject = { p1: { X: point1.X, Y: point1.Y }, p2: { X: point2.X, Y: point2.Y }, p3: { X: point3.X, Y: point3.Y } };
                        const otherInk = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke;
                        if (otherInk instanceof InkingStroke) {
                            const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
                            const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
                            if (doc.title === 'line') {
                                if (this.doesLineIntersectTriangle(otherScreenPts, triangleObject)) {
                                    docsToDelete.push(doc);
                                    hasDocInTriangle = true;
                                    cuspBooleanArray.push(true);
                                }
                            } else {
                                if (this.isAnyPointInTriangle(triangleObject, otherScreenPts)) {
                                    docsToDelete.push(doc);
                                    hasDocInTriangle = true;
                                    cuspBooleanArray.push(true);
                                }
                            }
                        }
                    }
                    cuspBooleanArray.push(hasDocInTriangle);
                }
                if (this.determineIfScribble(cuspBooleanArray)) {
                    docsToDelete.forEach(doc => {
                        ffView?.ComponentView?.removeDocument?.(doc);
                    });
                    this._points = [];
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * this method determines if what the user drew is a scribble based on certain criteria.
     * @param cuspBooleanArray will take in an array of booleans tht represent what triangles in the scribble
     * has an object in it. the 1st triangle is the 0th index, 2nd triangle is the 1st index and so on.
     * @returns
     */
    determineIfScribble(cuspBooleanArray: boolean[]) {
        if (!cuspBooleanArray) {
            return false;
        }
        const quarterArrayLength = Math.ceil((cuspBooleanArray.length - 2) * 0.25);
        let hasObjectInFirstAndLast25 = true;
        for (let i = 0; i < quarterArrayLength; i++) {
            if (cuspBooleanArray[i] == false || cuspBooleanArray[cuspBooleanArray.length - 1 - i] == false) {
                hasObjectInFirstAndLast25 = false;
            }
        }
        const trueCount = cuspBooleanArray.filter(value => value).length;
        const percentageTrues = trueCount / cuspBooleanArray.length;
        return percentageTrues > 0.65 || hasObjectInFirstAndLast25;
    }
    /**
     * determines if two rectangles are overlapping each other
     * @param rect1 the rectangle object has has a minX,maxX,minY, and maxY
     * @param rect2
     * @returns
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    isRectangleOverlap(rect1: any, rect2: any): boolean {
        const noOverlap = rect1.maxX < rect2.minX || rect1.minX > rect2.maxX || rect1.maxY < rect2.minY || rect1.minY > rect2.maxY;

        return !noOverlap;
    }
    /**
     * determines if there is a point in a triangle used to determine what triangles in the scribble have an object
     * @param pt the point in question
     * @param triangle the triangle with 3 points
     * @returns true or false if point is in triangle
     */
    isPointInTriangle(pt: { X: number; Y: number }, triangle: { p1: { X: number; Y: number }; p2: { X: number; Y: number }; p3: { X: number; Y: number } }): boolean {
        const area = (v1: { X: number; Y: number }, v2: { X: number; Y: number }, v3: { X: number; Y: number }) => Math.abs((v1.X * (v2.Y - v3.Y) + v2.X * (v3.Y - v1.Y) + v3.X * (v1.Y - v2.Y)) / 2.0);

        const A = area(triangle.p1, triangle.p2, triangle.p3);

        const A1 = area(pt, triangle.p2, triangle.p3);
        const A2 = area(triangle.p1, pt, triangle.p3);
        const A3 = area(triangle.p1, triangle.p2, pt);

        return A === A1 + A2 + A3;
    }
    /**
     * determines if any points in an array are in a triangle
     * @param triangle the triangle with 3 points
     * @param points the point in question
     * @returns true or false if point is in triangle
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    isAnyPointInTriangle(triangle: any, points: any[]): boolean {
        for (const point of points) {
            if (this.isPointInTriangle(point, triangle)) {
                return true;
            }
        }
        return false;
    }
    /**
     * determines if a line intersects a triangle. used for scribble gesture since the line doesnt have a lot
     * of points across is so isPointInTriangle will not work for it.
     * @param line is pointData
     * @param triangle triangle with 3 points
     * @returns true or false if it intersects
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    doesLineIntersectTriangle(line: any, triangle: any): boolean {
        const edges = [
            { start: triangle.p1, end: triangle.p2 },
            { start: triangle.p2, end: triangle.p3 },
            { start: triangle.p3, end: triangle.p1 },
        ];

        for (const edge of edges) {
            if (this.doLinesIntersect(line, edge)) {
                return true;
            }
        }

        return false;
    }
    /**
     * used in doesLineIntersectTriangle, splits up the triangle into 3 lines and runs this method
     * individually
     * @param line1 point data where 0th index is the coordinate of beginnning of line and last index is the coordinate of end of limne
     * @param line2 same thing
     * @returns
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    doLinesIntersect(line1: any, line2: any): boolean {
        const A = line1[0];
        const B = line1[line1.length - 1];
        const { start: C, end: D } = line2;
        const denominator = (B.X - A.X) * (D.Y - C.Y) - (B.Y - A.Y) * (D.X - C.X);
        if (denominator === 0) return false;

        const numerator1 = (A.Y - C.Y) * (D.X - C.X) - (A.X - C.X) * (D.Y - C.Y);
        const numerator2 = (A.Y - C.Y) * (B.X - A.X) - (A.X - C.X) * (B.Y - A.Y);

        const r = numerator1 / denominator;
        const s = numerator2 / denominator;

        return r >= 0 && r <= 1 && s >= 0 && s <= 1;
    }

    dryInk = () => {
        const newPoints = this._points.reduce((p, pts) => {
            p.push([pts.X, pts.Y]);
            return p;
        }, [] as number[][]);
        newPoints.pop();
        const controlPoints: { X: number; Y: number }[] = [];

        const bezierCurves = fitCurve.default(newPoints, 10);
        Array.from(bezierCurves).forEach(curve => {
            controlPoints.push({ X: curve[0][0], Y: curve[0][1] });
            controlPoints.push({ X: curve[1][0], Y: curve[1][1] });
            controlPoints.push({ X: curve[2][0], Y: curve[2][1] });
            controlPoints.push({ X: curve[3][0], Y: curve[3][1] });
        });
        const dist = Math.sqrt((controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y));
        if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0];
        this._points.length = 0;
        this._points.push(...controlPoints);
        this.dispatchGesture(Gestures.Stroke);
    };
    @action
    onPointerUp = () => {
        DocumentView.DownDocView = undefined;
        if (this._points.length > 1) {
            const B = this.svgBounds;
            const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
            const { Name, Score } =
                (this.InkShape
                    ? new Result(this.InkShape, 1, Date.now) 
                    : Doc.UserDoc().recognizeGestures && points.length > 2
                      ? GestureUtils.GestureRecognizer.Recognize([points])
                      : undefined) ?? 
                new Result(Gestures.Stroke, 1, Date.now); // prettier-ignore
            // if any of the shape is activated in the CollectionFreeFormViewChrome
            // need to decide when to turn gestures back on
            const actionPerformed = ((name: Gestures) => {
                switch (name) {
                    case Gestures.Line:
                    case Gestures.Triangle:
                    case Gestures.Rectangle:
                    case Gestures.Circle:
                        this.makeBezierPolygon(Name, true);
                        return this.dispatchGesture(name);
                    case Gestures.RightAngle:
                        return this.convertToText().length > 0;
                    default:
                    case Gestures.Stroke:
                        return this.isScribble();
                }
            })(Score < 0.7 ? Gestures.Stroke : (Name as Gestures));
            // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
            if (!actionPerformed) this.dryInk();
        }
        this._points.length = 0;
    };
    /**
     * used in the rightAngle gesture to convert handwriting into text. will only work on collections
     * TODO: make it work on individual ink docs.
     */
    convertToText() {
        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
        let minX = 999999999;
        let maxX = -999999999;
        let minY = 999999999;
        let maxY = -999999999;
        const textDocs: Doc[] = [];
        (ffView?.ComponentView as CollectionFreeFormView).childDocs
            .filter(doc => doc.type === DocumentType.COL)
            .forEach(doc => {
                if (typeof doc.width === 'number' && typeof doc.height === 'number' && typeof doc.x === 'number' && typeof doc.y === 'number') {
                    const bounds = DocumentView.getDocumentView(doc)?.getBounds;
                    if (bounds) {
                        const rect1 = { minX: bounds.left, maxX: bounds.right, minY: bounds.top, maxY: bounds.bottom };
                        if (this.isRectangleOverlap(rect1, this.getExtremeCoordinates())) {
                            if (doc.x < minX) {
                                minX = doc.x;
                            }
                            if (doc.x > maxX) {
                                maxX = doc.x;
                            }
                            if (doc.y < minY) {
                                minY = doc.y;
                            }
                            if (doc.y + doc.height > maxY) {
                                maxY = doc.y + doc.height;
                            }
                            const newDoc = Docs.Create.TextDocument(doc.transcription as string, { title: '', x: doc.x as number, y: minY });
                            newDoc.height = doc.height;
                            newDoc.width = doc.width;
                            if (ffView?.ComponentView?.addDocument && ffView?.ComponentView?.removeDocument) {
                                ffView.ComponentView.addDocument(newDoc);
                                ffView.ComponentView.removeDocument(doc);
                            }
                            textDocs.push(newDoc);
                        }
                    }
                }
            });
        return textDocs;
    }
    /**
     * used to determine how many cusps and where the cusps are in order
     * @returns will return an array containing the coordinates of the sharp cusps
     */
    getCusps() {
        const points = this._points.map(p => ({ X: p.X, Y: p.Y }));
        const arrayOfPoints: { X: number; Y: number }[] = [];
        arrayOfPoints.push(points[0]);
        for (let i = 0; i < points.length - 2; i++) {
            const point1 = points[i];
            const point2 = points[i + 1];
            const point3 = points[i + 2];
            if (this.find_angle(point1, point2, point3) < 90) {
                arrayOfPoints.push(point2);
            }
        }
        arrayOfPoints.push(points[points.length - 1]);
        return arrayOfPoints;
    }
    /**
     * will look through an array of point data and return the coordinates of the smallest box that can fit all the points
     * @returns the minX,maxX,minY,maxY of the box
     */
    getExtremeCoordinates() {
        const coordinates = this._points;
        if (coordinates.length === 0) {
            throw new Error('Coordinates array is empty');
        }

        let minX = coordinates[0].X;
        let maxX = coordinates[0].X;
        let minY = coordinates[0].Y;
        let maxY = coordinates[0].Y;

        coordinates.forEach(coord => {
            if (coord.X < minX) minX = coord.X;
            if (coord.X > maxX) maxX = coord.X;
            if (coord.Y < minY) minY = coord.Y;
            if (coord.Y > maxY) maxY = coord.Y;
        });
        return {
            minX,
            maxX,
            minY,
            maxY,
        };
    }
    /**
     * takes in three points and then determines the angle of the points. used to determine if the cusp
     * is sharp enoug
     * @returns
     */
    find_angle(A: { X: number; Y: number }, B: { X: number; Y: number }, C: { X: number; Y: number }) {
        const AB = Math.sqrt(Math.pow(B.X - A.X, 2) + Math.pow(B.Y - A.Y, 2));
        const BC = Math.sqrt(Math.pow(B.X - C.X, 2) + Math.pow(B.Y - C.Y, 2));
        const AC = Math.sqrt(Math.pow(C.X - A.X, 2) + Math.pow(C.Y - A.Y, 2));
        return Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB)) * (180 / Math.PI);
    }
    makeBezierPolygon = (shape: string, gesture: boolean) => {
        const xs = this._points.map(p => p.X);
        const ys = this._points.map(p => p.Y);
        let right = Math.max(...xs);
        let left = Math.min(...xs);
        let bottom = Math.max(...ys);
        let top = Math.min(...ys);
        const firstx = this._points[0].X;
        const firsty = this._points[0].Y;
        let lastx = this._points[this._points.length - 2].X;
        let lasty = this._points[this._points.length - 2].Y;
        let fourth = (lastx - firstx) / 4;
        if (isNaN(fourth) || fourth === 0) {
            fourth = 0.01;
        }
        let m = (lasty - firsty) / (lastx - firstx);
        if (isNaN(m) || m === 0) {
            m = 0.01;
        }
        // const b = firsty - m * firstx;
        if (shape === 'noRec') {
            return false;
        }
        if (!gesture) {
            // if shape options is activated in inkOptionMenu
            // take second to last point because _point[length-1] is _points[0]
            right = this._points[this._points.length - 2].X;
            left = this._points[0].X;
            bottom = this._points[this._points.length - 2].Y;
            top = this._points[0].Y;
            if (shape !== Gestures.Arrow && shape !== Gestures.Line && shape !== Gestures.Circle) {
                if (left > right) {
                    const temp = right;
                    right = left;
                    left = temp;
                }
                if (top > bottom) {
                    const temp = top;
                    top = bottom;
                    bottom = temp;
                }
            }
        }
        this._points.length = 0;
        switch (shape) {
            case Gestures.Rectangle:
                this._points.push({ X: left, Y: top });
                this._points.push({ X: left, Y: top });
                this._points.push({ X: right, Y: top });
                this._points.push({ X: right, Y: top });

                this._points.push({ X: right, Y: top });
                this._points.push({ X: right, Y: top });
                this._points.push({ X: right, Y: bottom });
                this._points.push({ X: right, Y: bottom });

                this._points.push({ X: right, Y: bottom });
                this._points.push({ X: right, Y: bottom });
                this._points.push({ X: left, Y: bottom });
                this._points.push({ X: left, Y: bottom });

                this._points.push({ X: left, Y: bottom });
                this._points.push({ X: left, Y: bottom });
                this._points.push({ X: left, Y: top });
                this._points.push({ X: left, Y: top });

                break;

            case Gestures.Triangle:
                this._points.push({ X: left, Y: bottom });
                this._points.push({ X: left, Y: bottom });

                this._points.push({ X: right, Y: bottom });
                this._points.push({ X: right, Y: bottom });
                this._points.push({ X: right, Y: bottom });
                this._points.push({ X: right, Y: bottom });

                this._points.push({ X: (right + left) / 2, Y: top });
                this._points.push({ X: (right + left) / 2, Y: top });
                this._points.push({ X: (right + left) / 2, Y: top });
                this._points.push({ X: (right + left) / 2, Y: top });

                this._points.push({ X: left, Y: bottom });
                this._points.push({ X: left, Y: bottom });

                break;
            case Gestures.Circle:
                {
                    // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
                    // making the curves indistinguishable from a circle.
                    // Source: https://spencermortensen.com/articles/bezier-circle/
                    const c = 0.551915024494;
                    const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
                    const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
                    const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));

                    // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.
                    this._points.push({ X: centerX, Y: centerY + radius });
                    this._points.push({ X: centerX + c * radius, Y: centerY + radius });
                    this._points.push({ X: centerX + radius, Y: centerY + c * radius });
                    this._points.push({ X: centerX + radius, Y: centerY });

                    this._points.push({ X: centerX + radius, Y: centerY });
                    this._points.push({ X: centerX + radius, Y: centerY - c * radius });
                    this._points.push({ X: centerX + c * radius, Y: centerY - radius });
                    this._points.push({ X: centerX, Y: centerY - radius });

                    this._points.push({ X: centerX, Y: centerY - radius });
                    this._points.push({ X: centerX - c * radius, Y: centerY - radius });
                    this._points.push({ X: centerX - radius, Y: centerY - c * radius });
                    this._points.push({ X: centerX - radius, Y: centerY });

                    this._points.push({ X: centerX - radius, Y: centerY });
                    this._points.push({ X: centerX - radius, Y: centerY + c * radius });
                    this._points.push({ X: centerX - c * radius, Y: centerY + radius });
                    this._points.push({ X: centerX, Y: centerY + radius });
                }
                break;

            case Gestures.Line:
                if (Math.abs(firstx - lastx) < 10 && Math.abs(firsty - lasty) > 10) {
                    lastx = firstx;
                }
                if (Math.abs(firsty - lasty) < 10 && Math.abs(firstx - lastx) > 10) {
                    lasty = firsty;
                }
                this._points.push({ X: firstx, Y: firsty });
                this._points.push({ X: firstx, Y: firsty });

                this._points.push({ X: lastx, Y: lasty });
                this._points.push({ X: lastx, Y: lasty });
                break;
            case Gestures.Arrow:
                {
                    const x1 = left;
                    const y1 = top;
                    const x2 = right;
                    const y2 = bottom;
                    const L1 = Math.sqrt(Math.abs(x1 - x2) ** 2 + Math.abs(y1 - y2) ** 2);
                    const L2 = L1 / 5;
                    const angle = 0.785398;
                    const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
                    const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle));
                    const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle));
                    const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle));
                    this._points.push({ X: x1, Y: y1 });
                    this._points.push({ X: x2, Y: y2 });
                    this._points.push({ X: x3, Y: y3 });
                    this._points.push({ X: x4, Y: y4 });
                    this._points.push({ X: x2, Y: y2 });
                }
                break;
            default:
        }
        return false;
    };

    dispatchGesture = (gesture: Gestures, stroke?: InkData, text?: string) => {
        const points = (stroke ?? this._points).slice();
        return (
            document.elementFromPoint(points[0].X, points[0].Y)?.dispatchEvent(
                new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
                    bubbles: true,
                    detail: {
                        points,
                        gesture,
                        bounds: InkField.getBounds(points),
                        text,
                    },
                })
            ) || false
        );
    };

    @computed get svgBounds() {
        return InkField.getBounds(this._points);
    }

    get elements() {
        const selView = DocumentView.DownDocView;
        const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.screenToViewTransform().Scale || 1);
        const rect = this._overlayRef.current?.getBoundingClientRect();
        const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(this._points, true);
        B.left -= width / 2;
        B.right += width / 2;
        B.top = B.top - width / 2 - (rect?.y || 0);
        B.bottom += width / 2;
        B.width += width;
        B.height += width;
        const fillColor = ActiveFillColor();
        const strokeColor = fillColor && fillColor !== 'transparent' ? fillColor : ActiveInkColor();
        return [
            this.props.children,
            this._palette,
            [
                this._strokes.map((l, i) => {
                    const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(l, true);
                    return (
                        <svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
                            {InteractionUtils.CreatePolyline(
                                l,
                                b.left,
                                b.top,
                                strokeColor,
                                width,
                                width,
                                'miter',
                                'round',
                                ActiveInkBezierApprox(),
                                'none' /* ActiveFillColor() */,
                                ActiveArrowStart(),
                                ActiveArrowEnd(),
                                ActiveArrowScale(),
                                ActiveDash(),
                                1,
                                1,
                                this.InkShape as Gestures,
                                'none',
                                1.0,
                                false
                            )}
                        </svg>
                    );
                }),
                this._points.length <= 1 ? null : (
                    <svg key="svg" width={B.width} height={B.height} style={{ top: 0, left: 0, transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
                        {InteractionUtils.CreatePolyline(
                            this._points.map(p => ({ X: p.X - (rect?.x || 0), Y: p.Y - (rect?.y || 0) })),
                            B.left,
                            B.top,
                            ActiveInkColor(),
                            width,
                            width,
                            'miter',
                            'round',
                            '',
                            'none' /* ActiveFillColor() */,
                            ActiveArrowStart(),
                            ActiveArrowEnd(),
                            ActiveArrowScale(),
                            ActiveDash(),
                            1,
                            1,
                            this.InkShape as Gestures,
                            'none',
                            1.0,
                            false
                        )}
                    </svg>
                ),
            ],
        ];
    }
    screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1);
    return300 = () => 300;
    @action
    public openFloatingDoc = (doc: Doc) => {
        this._clipboardDoc = (
            <DocumentView
                Document={doc}
                addDocument={undefined}
                addDocTab={returnFalse}
                pinToPres={emptyFunction}
                removeDocument={undefined}
                ScreenToLocalTransform={this.screenToLocalTransform}
                PanelWidth={this.return300}
                PanelHeight={this.return300}
                isDocumentActive={returnFalse}
                isContentActive={returnFalse}
                renderDepth={0}
                styleProvider={returnEmptyString}
                containerViewPath={returnEmptyDocViewList}
                focus={emptyFunction}
                whenChildContentsActiveChanged={emptyFunction}
                childFiltersByRanges={returnEmptyFilter}
                childFilters={returnEmptyFilter}
                searchFilterDocs={returnEmptyDoclist}
            />
        );
    };

    @action
    public closeFloatingDoc = () => {
        this._clipboardDoc = undefined;
    };

    render() {
        return (
            <div className="gestureOverlay-cont" style={{ pointerEvents: this._props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown}>
                {this.elements}

                <div
                    className="clipboardDoc-cont"
                    style={{
                        height: this.height,
                        width: this.height,
                        pointerEvents: this._clipboardDoc ? 'unset' : 'none',
                        touchAction: 'none',
                        transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height} px)`,
                    }}>
                    {this._clipboardDoc}
                </div>
                <div
                    className="filter-cont"
                    style={{
                        transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height}px)`,
                        height: this.height,
                        width: this.height,
                        pointerEvents: 'none',
                        touchAction: 'none',
                        display: this.showBounds ? 'unset' : 'none',
                    }}
                />
            </div>
        );
    }
}

ScriptingGlobals.add('GestureOverlay', GestureOverlay);
ScriptingGlobals.add(function setPen(width: string, color: string, fill: string, arrowStart: string, arrowEnd: string, dash: string) {
    runInAction(() => {
        GestureOverlay.Instance.SavedColor = ActiveInkColor();
        SetActiveInkColor(color);
        GestureOverlay.Instance.SavedWidth = ActiveInkWidth();
        SetActiveInkWidth(width);
        SetActiveFillColor(fill);
        SetActiveArrowStart(arrowStart);
        SetActiveArrowStart(arrowEnd);
        SetActiveDash(dash);
    });
});
ScriptingGlobals.add(function resetPen() {
    runInAction(() => {
        SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? 'rgb(0, 0, 0)');
        SetActiveInkWidth(GestureOverlay.Instance.SavedWidth?.toString() ?? '2');
    });
}, 'resets the pen tool');
ScriptingGlobals.add(
    function createText(text: string, X: number, Y: number) {
        GestureOverlay.Instance.dispatchGesture(Gestures.Text, [{ X, Y }], text);
    },
    'creates a text document with inputted text and coordinates',
    '(text: any, x: any, y: any)'
);
