import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
import ReactLoading from 'react-loading';
import { returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
import { Utils, emptyFunction } from '../../Utils';
import { Doc } from '../../fields/Doc';
import { Height, Width } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { NumCast, toList } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
import { DragManager } from '../util/DragManager';
import { dropActionType } from '../util/DropActionTypes';
import { Transform } from '../util/Transform';
import { LightboxView } from './LightboxView';
import { ObservableReactComponent } from './ObservableReactComponent';
import './OverlayView.scss';
import { DefaultStyleProvider } from './StyleProvider';
import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';

const _global = (window /* browser */ || global) /* node */ as any;

export type OverlayDisposer = () => void;

export type OverlayElementOptions = {
    x: number;
    y: number;
    width?: number;
    height?: number;
    title?: string;
};

export interface OverlayWindowProps {
    children: JSX.Element;
    overlayOptions: OverlayElementOptions;
    onClick: () => void;
}

@observer
export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps> {
    @observable x: number = 0;
    @observable y: number = 0;
    @observable width: number = 0;
    @observable height: number = 0;
    constructor(props: OverlayWindowProps) {
        super(props);
        makeObservable(this);
        const opts = props.overlayOptions;
        this.x = opts.x;
        this.y = opts.y;
        this.width = opts.width || 200;
        this.height = opts.height || 200;
    }

    onPointerDown = () => {
        document.removeEventListener('pointermove', this.onPointerMove);
        document.removeEventListener('pointerup', this.onPointerUp);
        document.addEventListener('pointermove', this.onPointerMove);
        document.addEventListener('pointerup', this.onPointerUp);
    };

    onResizerPointerDown = () => {
        document.removeEventListener('pointermove', this.onResizerPointerMove);
        document.removeEventListener('pointerup', this.onResizerPointerUp);
        document.addEventListener('pointermove', this.onResizerPointerMove);
        document.addEventListener('pointerup', this.onResizerPointerUp);
    };

    @action
    onPointerMove = (e: PointerEvent) => {
        this.x += e.movementX;
        this.x = Math.max(Math.min(this.x, window.innerWidth - this.width), 0);
        this.y += e.movementY;
        this.y = Math.max(Math.min(this.y, window.innerHeight - this.height), 0);
    };

    @action
    onResizerPointerMove = (e: PointerEvent) => {
        this.width += e.movementX;
        this.width = Math.max(this.width, 30);
        this.height += e.movementY;
        this.height = Math.max(this.height, 30);
    };

    onPointerUp = () => {
        document.removeEventListener('pointermove', this.onPointerMove);
        document.removeEventListener('pointerup', this.onPointerUp);
    };

    onResizerPointerUp = () => {
        document.removeEventListener('pointermove', this.onResizerPointerMove);
        document.removeEventListener('pointerup', this.onResizerPointerUp);
    };

    render() {
        return LightboxView.LightboxDoc ? null : (
            <div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
                <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown}>
                    {this._props.overlayOptions.title || 'Untitled'}
                    <button type="button" onClick={this._props.onClick} className="overlayWindow-closeButton">
                        X
                    </button>
                </div>
                <div className="overlayWindow-content">{this.props.children}</div>
                <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown} />
            </div>
        );
    }
}

@observer
export class OverlayView extends ObservableReactComponent<{}> {
    // eslint-disable-next-line no-use-before-define
    public static Instance: OverlayView;
    @observable.shallow _elements: JSX.Element[] = [];

    constructor(props: any) {
        super(props);
        makeObservable(this);
        if (!OverlayView.Instance) {
            OverlayView.Instance = this;
            new _global.ResizeObserver(
                action((entries: any) => {
                    Array.from(entries).forEach((entry: any) => {
                        Doc.MyOverlayDocs.forEach(docIn => {
                            const doc = docIn;
                            if (NumCast(doc.overlayX) > entry.contentRect.width - 10) {
                                doc.overlayX = entry.contentRect.width - 10;
                            }
                            if (NumCast(doc.overlayY) > entry.contentRect.height - 10) {
                                doc.overlayY = entry.contentRect.height - 10;
                            }
                        });
                    });
                })
            ).observe(window.document.body);
        }
    }

    @action
    addElement(ele: JSX.Element, options: OverlayElementOptions): OverlayDisposer {
        const div = (
            <div
                key={Utils.GenerateGuid()}
                className="overlayView-wrapperDiv"
                style={{
                    transform: `translate(${options.x}px, ${options.y}px)`,
                    width: options.width,
                    height: options.height,
                    top: 0,
                    left: 0,
                }}>
                {ele}
            </div>
        );
        this._elements.push(div);
        return action(() => {
            const index = this._elements.indexOf(div);
            if (index !== -1) this._elements.splice(index, 1);
        });
    }

    @action
    addWindow(contents: JSX.Element, options: OverlayElementOptions): OverlayDisposer {
        const remove = action(() => {
            const index = this._elements.indexOf(contents);
            if (index !== -1) this._elements.splice(index, 1);
        });
        const wincontents = (
            <OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}>
                {contents}
            </OverlayWindow>
        );
        this._elements.push(wincontents);
        return remove;
    }

    removeOverlayDoc = (docs: Doc | Doc[]) => toList(docs).every(Doc.RemFromMyOverlay);

    docScreenToLocalXf = computedFn((doc: Doc) => () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1));

    @computed get overlayDocs() {
        return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => {
            let [offsetx, offsety] = [0, 0];
            const dref = React.createRef<HTMLDivElement>();
            const onPointerMove = action((e: PointerEvent, down: number[]) => {
                if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
                if (e.buttons === 1) {
                    d.overlayX = e.clientX + offsetx;
                    d.overlayY = e.clientY + offsety;
                }
                if (e.metaKey) {
                    const dragData = new DragManager.DocumentDragData([d]);
                    dragData.offset = [-offsetx, -offsety];
                    dragData.dropAction = dropActionType.move;
                    dragData.removeDocument = this.removeOverlayDoc;
                    dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => (dragData.removeDocument?.(doc) ? addDocument(doc) : false);
                    DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
                    return true;
                }
                return false;
            });

            const onPointerDown = (e: React.PointerEvent) => {
                setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction, false);
                offsetx = NumCast(d.overlayX) - e.clientX;
                offsety = NumCast(d.overlayY) - e.clientY;
            };
            return (
                <div
                    className="overlayView-doc"
                    ref={dref}
                    key={d[Id]}
                    onPointerDown={onPointerDown}
                    style={{ top: d.type === DocumentType.PRES ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.overlayX}px, ${d.overlayY}px)` }}>
                    <DocumentView
                        Document={d}
                        addDocument={undefined}
                        removeDocument={this.removeOverlayDoc}
                        PanelWidth={d[Width]}
                        PanelHeight={d[Height]}
                        ScreenToLocalTransform={this.docScreenToLocalXf(d)}
                        renderDepth={1}
                        hideDecorations
                        isDocumentActive={returnTrue}
                        isContentActive={returnTrue}
                        whenChildContentsActiveChanged={emptyFunction}
                        focus={emptyFunction}
                        styleProvider={DefaultStyleProvider}
                        containerViewPath={returnEmptyDoclist}
                        addDocTab={DocumentViewInternal.addDocTabFunc}
                        pinToPres={emptyFunction}
                        childFilters={returnEmptyFilter}
                        childFiltersByRanges={returnEmptyFilter}
                        searchFilterDocs={returnEmptyDoclist}
                    />
                </div>
            );
        });
    }

    public static ShowSpinner() {
        return OverlayView.Instance.addElement(<ReactLoading type="spinningBubbles" color="green" height={250} width={250} />, { x: 300, y: 200 });
    }

    render() {
        return (
            <div className="overlayView" id="overlayView">
                <div>{this._elements}</div>
                {this.overlayDocs}
            </div>
        );
    }
}
