import React = require("react");
import { action, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../fields/Doc";
import { documentSchema } from "../../fields/documentSchemas";
import { InkData, InkField, InkTool } from "../../fields/InkField";
import { makeInterface } from "../../fields/Schema";
import { Cast, NumCast, StrCast } from "../../fields/Types";
import { TraceMobx } from "../../fields/util";
import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InteractionUtils } from "../util/InteractionUtils";
import { Scripting } from "../util/Scripting";
import { ContextMenu } from "./ContextMenu";
import { ViewBoxBaseComponent } from "./DocComponent";
import { GestureOverlay } from "./GestureOverlay";
import { Colors } from "./global/globalEnums";
import { InkControls } from "./InkControls";
import { InkHandles } from "./InkHandles";
import "./InkStroke.scss";
import { InkStrokeProperties } from "./InkStrokeProperties";
import { FieldView, FieldViewProps } from "./nodes/FieldView";

type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);

@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
    static readonly MaskDim = 50000;
    @observable private _properties?: InkStrokeProperties;
    _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
    _selDisposer: IReactionDisposer | undefined;

    constructor(props: FieldViewProps & InkDocument) {
        super(props);

        this._properties = InkStrokeProperties.Instance;
    }

    componentDidMount() {
        this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
            selected => !selected && this.toggleControlButton());
    }
    componentWillUnmount() {
        this._selDisposer?.();
    }

    public static LayoutString(fieldStr: string) {
        return FieldView.LayoutString(InkingStroke, fieldStr);
    }

    analyzeStrokes() {
        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
        CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
    }

    public static toggleMask = action((inkDoc: Doc) => {
        inkDoc.isInkMask = !inkDoc.isInkMask;
        inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
        inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
        inkDoc.color = "#9b9b9bff";
        inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
    });

    onClick = (e: React.MouseEvent) => {
        if (this._handledClick) {
            e.stopPropagation(); //stop the event so that docView won't open the lightbox
        }
    }

    /**
     * Handles the movement of the entire ink object when the user clicks and drags.
     */
    onPointerDown = (e: React.PointerEvent) => {
        this._handledClick = false;
        if (this.props.isSelected(true)) {
            setupMoveUpEvents(this, e, returnFalse, emptyFunction,
                action((e: PointerEvent, doubleTap: boolean | undefined) => {
                    doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
                    if (doubleTap && this._properties) {
                        this._properties._controlButton = true;
                        this._handledClick = true;  // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
                    }
                }), this._properties?._controlButton, this._properties?._controlButton
            );
        }
    }

    /**
     * Ensures the ink controls and handles aren't rendered when the current ink stroke is reselected.
     */
    @action
    toggleControlButton = () => {
        if (!this.props.isSelected() && this._properties) {
            this._properties._controlButton = false;
        }
    }

    render() {
        TraceMobx();
        // Extracting the ink data and formatting information of the current ink stroke.
        // console.log(InkingStroke.InkShape);
        const InkShape = GestureOverlay.Instance.InkShape;
        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
        const inkDoc: Doc = this.layoutDoc;
        const strokeWidth = Number(this.layoutDoc.strokeWidth);
        const lineTop = Math.min(...data.map(p => p.Y));
        const lineBottom = Math.max(...data.map(p => p.Y));
        const lineLeft = Math.min(...data.map(p => p.X));
        const lineRight = Math.max(...data.map(p => p.X));
        const left = lineLeft - strokeWidth / 2;
        const top = lineTop - strokeWidth / 2;
        const right = lineRight + strokeWidth / 2;
        const bottom = lineBottom + strokeWidth / 2;
        const width = Math.max(1, right - left);
        const height = Math.max(1, bottom - top);
        const scaleX = width === strokeWidth ? 1 : (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
        const scaleY = height === strokeWidth ? 1 : (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
        const strokeColor = StrCast(this.layoutDoc.color, "");
        const dotsize = Math.max(width * scaleX, height * scaleY) / 40;

        // Visually renders the polygonal line made by the user.
        const inkLine = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
            StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
        // Thin blue line indicating that the current ink stroke is selected.
        const selectedLine = InteractionUtils.CreatePolyline(data, left, top, Colors.MEDIUM_BLUE, strokeWidth, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"),
            StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5 && lineBottom - lineTop > 1 && lineRight - lineLeft > 1, false);
        // Invisible polygonal line that enables the ink to be selected by the user.
        const clickableLine = InteractionUtils.CreatePolyline(data, left, top, "transparent", strokeWidth, strokeWidth + 15, StrCast(this.layoutDoc.strokeBezier),
            StrCast(this.layoutDoc.fillColor, "none"), "none", "none", undefined, scaleX, scaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", false, true);
        // Set of points rendered upon the ink that can be added if a user clicks on one.
        const addedPoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker),
            StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);

        return (
            <svg className="inkStroke"
                style={{
                    pointerEvents: "none",
                    transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
                    mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
                    overflow: "visible",
                }}
                onPointerDown={this.onPointerDown}
                onClick={this.onClick}
                onContextMenu={() => {
                    const cm = ContextMenu.Instance;
                    if (cm) {
                        !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
                        cm.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
                        cm.addItem({ description: "Edit Points", event: action(() => { if (this._properties) { this._properties._controlButton = !this._properties._controlButton; } }), icon: "paint-brush" });
                    }
                }}
            >

                {clickableLine}
                {inkLine}
                {this.props.isSelected() ? selectedLine : ""}
                {this.props.isSelected() && this._properties?._controlButton ?
                    <>
                        <InkControls
                            inkDoc={inkDoc}
                            data={data}
                            addedPoints={addedPoints}
                            format={[left, top, scaleX, scaleY, strokeWidth]}
                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                        <InkHandles
                            inkDoc={inkDoc}
                            data={data}
                            shape={InkShape}
                            format={[left, top, scaleX, scaleY, strokeWidth]}
                            ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                    </> : ""}
            </svg>
        );
    }
}


export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); }
export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); }
export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); }
export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); }
export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); }
export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); }
export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
export function ActiveInkPen(): Doc { return Doc.UserDoc(); }
export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
export function ActiveInkWidth(): number { return NumCast(ActiveInkPen()?.activeInkWidth); }
export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
    CurrentUserUtils.SelectedTool = pen ? InkTool.Highlighter : InkTool.None;
    SetActiveInkWidth(width);
    SetActiveInkColor(color);
    SetActiveFillColor(fill);
    SetActiveArrowStart(arrowStart);
    SetActiveArrowEnd(arrowEnd);
    SetActiveDash(dash);
});
Scripting.addGlobal(function activateEraser(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Eraser : InkTool.None; });
Scripting.addGlobal(function activateStamp(pen: any) { return CurrentUserUtils.SelectedTool = pen ? InkTool.Stamp : InkTool.None; });
Scripting.addGlobal(function deactivateInk() { return CurrentUserUtils.SelectedTool = InkTool.None; });
Scripting.addGlobal(function setInkWidth(width: any) { return SetActiveInkWidth(width); });
Scripting.addGlobal(function setInkColor(color: any) { return SetActiveInkColor(color); });
Scripting.addGlobal(function setFillColor(fill: any) { return SetActiveFillColor(fill); });
Scripting.addGlobal(function setActiveArrowStart(arrowStart: any) { return SetActiveArrowStart(arrowStart); });
Scripting.addGlobal(function setActiveArrowEnd(arrowEnd: any) { return SetActiveArrowStart(arrowEnd); });
Scripting.addGlobal(function setActiveDash(dash: any) { return SetActiveDash(dash); });
