import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
import { List } from "../../../new_fields/List";
import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { OmitKeys, Utils } from "../../../Utils";
import { DocumentManager } from "../../util/DocumentManager";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { UndoManager } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { DocComponent } from "../DocComponent";
import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
import "./DocumentView.scss";
import React = require("react");

export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
}

const schema = createSchema({
    zoomBasis: "number",
    zIndex: "number",
});

//TODO Types: The import order is wrong, so positionSchema is undefined
type FreeformDocument = makeInterface<[typeof schema, typeof positionSchema]>;
const FreeformDocument = makeInterface(schema, positionSchema);

@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
    private _mainCont = React.createRef<HTMLDivElement>();
    private _downX: number = 0;
    private _downY: number = 0;
    _bringToFrontDisposer?: IReactionDisposer;

    @computed get transform() {
        return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `;
    }

    @computed get X() { return FieldValue(this.Document.x, 0); }
    @computed get Y() { return FieldValue(this.Document.y, 0); }
    @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); }
    @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); }
    @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); }
    @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.width, 0); }
    @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.height, 0); }

    set width(w: number) {
        this.Document.width = w;
        if (this.nativeWidth && this.nativeHeight) {
            this.Document.height = this.nativeHeight / this.nativeWidth * w;
        }
    }
    set height(h: number) {
        this.Document.height = h;
        if (this.nativeWidth && this.nativeHeight) {
            this.Document.width = this.nativeWidth / this.nativeHeight * h;
        }
    }
    contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
    panelWidth = () => this.props.PanelWidth();
    panelHeight = () => this.props.PanelHeight();
    toggleMinimized = async () => this.toggleIcon(await DocListCastAsync(this.props.Document.maximizedDocs));
    getTransform = (): Transform => this.props.ScreenToLocalTransform()
        .translate(-this.X, -this.Y)
        .scale(1 / this.contentScaling()).scale(1 / this.zoom)

    @computed
    get docView() {
        return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit}
            toggleMinimized={this.toggleMinimized}
            ContentScaling={this.contentScaling}
            ScreenToLocalTransform={this.getTransform}
            PanelWidth={this.panelWidth}
            PanelHeight={this.panelHeight}
        />;
    }

    componentDidMount() {
        this._bringToFrontDisposer = reaction(() => this.props.Document.isIconAnimating, (values) => {
            this.props.bringToFront(this.props.Document);
            if (values instanceof List) {
                let scrpt = this.props.ScreenToLocalTransform().transformPoint(values[0], values[1]);
                this.animateBetweenIcon(true, scrpt, [this.Document.x || 0, this.Document.y || 0],
                    this.Document.width || 0, this.Document.height || 0, values[2], values[3] ? true : false);
            }
        }, { fireImmediately: true });
    }

    componentWillUnmount() {
        if (this._bringToFrontDisposer) this._bringToFrontDisposer();
    }

    animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, maximizing: boolean) {

        setTimeout(() => {
            let now = Date.now();
            let progress = Math.min(1, (now - stime) / 200);
            let pval = maximizing ?
                [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] :
                [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress];
            this.props.Document.width = maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress;
            this.props.Document.height = maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress;
            this.props.Document.x = pval[0];
            this.props.Document.y = pval[1];
            if (first) {
                this.props.Document.proto!.willMaximize = false;
            }
            if (now < stime + 200) {
                this.animateBetweenIcon(false, icon, targ, width, height, stime, maximizing);
            }
            else {
                if (!maximizing) {
                    this.props.Document.proto!.isMinimized = true;
                    this.props.Document.x = targ[0];
                    this.props.Document.y = targ[1];
                    this.props.Document.width = width;
                    this.props.Document.height = height;
                }
                this.props.Document.proto!.isIconAnimating = undefined;
            }
        },
            2);
    }
    @action
    public toggleIcon = async (maximizedDocs: Doc[] | undefined): Promise<void> => {
        SelectionManager.DeselectAll();
        let isMinimized: boolean | undefined;
        let minimizedDoc: Doc | undefined = this.props.Document;
        if (!maximizedDocs) {
            minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
            if (minimizedDoc) maximizedDocs = await DocListCastAsync(minimizedDoc.maximizedDocs);
        }
        if (minimizedDoc && maximizedDocs) {
            let minimizedTarget = minimizedDoc;
            if (!CollectionFreeFormDocumentView._undoBatch) {
                CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
            }
            maximizedDocs.map(maximizedDoc => {
                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
                if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
                    if (isMinimized === undefined) {
                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
                    }
                    let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) / 2;
                    let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) / 2;
                    if (minx !== undefined && miny !== undefined) {
                        let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
                        maximizedDoc.willMaximize = isMinimized;
                        maximizedDoc.isMinimized = false;
                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
                    }
                }
            });
            setTimeout(() => {
                CollectionFreeFormDocumentView._undoBatch && CollectionFreeFormDocumentView._undoBatch.end();
                CollectionFreeFormDocumentView._undoBatch = undefined;
            }, 500);
        }
    }
    static _undoBatch?: UndoManager.Batch = undefined;
    onPointerDown = (e: React.PointerEvent): void => {
        this._downX = e.clientX;
        this._downY = e.clientY;
        if (e.button === 0 && e.altKey) {
            e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started
        } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document 
    }
    onClick = async (e: React.MouseEvent) => {
        e.stopPropagation();
        let altKey = e.altKey;
        if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
            Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
            let isExpander = (e.target as any).id === "isExpander";
            if (BoolCast(this.props.Document.isButton, false) || isExpander) {
                let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
                let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
                let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
                let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []);
                let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []);
                let expandedDocs: Doc[] = [];
                expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
                expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
                expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []),
                // ...(maximizedDocs ? maximizedDocs : []),
                // ...(summarizedDocs ? summarizedDocs : []),];
                if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                    let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0]);
                    if (!hasView && ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight))) {
                        let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
                        if (dataDocs) {
                            SelectionManager.DeselectAll();
                            expandedDocs.forEach(maxDoc => {
                                maxDoc.isMinimized = false;
                                if (!CollectionDockingView.Instance.CloseRightSplit(maxDoc)) {
                                    CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(maxDoc));
                                }
                            });
                        }
                    } else {
                        //if (altKey) this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(maxDoc, false));
                        this.toggleIcon(expandedDocs);
                    }
                }
                else if (linkedToDocs.length || linkedFromDocs.length) {
                    let linkedFwdDocs = [
                        linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
                        linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
                    if (linkedFwdDocs) {
                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0]);
                    }
                }
            }
        }
    }

    onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
    onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };

    borderRounding = () => {
        let br = NumCast(this.props.Document.borderRounding);
        return br >= 0 ? br :
            NumCast(this.props.Document.nativeWidth) === 0 ?
                Math.min(this.props.PanelWidth(), this.props.PanelHeight())
                : Math.min(this.Document.nativeWidth || 0, this.Document.nativeHeight || 0);
    }

    render() {
        let maximizedDoc = FieldValue(Cast(this.props.Document.maximizedDocs, listSpec(Doc)));
        let zoomFade = 1;
        //var zoom = doc.GetNumber(KeyStore.ZoomBasis, 1);
        let transform = this.getTransform().scale(this.contentScaling()).inverse();
        var [sptX, sptY] = transform.transformPoint(0, 0);
        let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
        let w = bptX - sptX;
        //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
        const screenWidth = Math.min(50 * NumCast(this.props.Document.nativeWidth, 0), 1800);
        let fadeUp = .75 * screenWidth;
        let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth;
        zoomFade = w < fadeDown  /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? Math.sqrt(Math.sqrt(fadeDown / w)) : w / fadeUp))) : 1;

        return (
            <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
                onPointerDown={this.onPointerDown}
                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter}
                onClick={this.onClick}
                style={{
                    outlineColor: "maroon",
                    outlineStyle: "dashed",
                    outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ||
                        BoolCast(this.props.Document.protoBrush, false) ?
                        `${1 * this.getTransform().Scale}px` : "0px",
                    opacity: zoomFade,
                    borderRadius: `${this.borderRounding()}px`,
                    transformOrigin: "left top",
                    transform: this.transform,
                    pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
                    width: this.width,
                    height: this.height,
                    position: "absolute",
                    zIndex: this.Document.zIndex || 0,
                    backgroundColor: "transparent"
                }} >
                {this.docView}
            </div>
        );
    }
}