import { action, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { lightOrDark, returnFalse } from '../../Utils';
import { Doc, Opt } from '../../fields/Doc';
import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
import { ImageUtils } from '../util/Import & Export/ImageUtils';
import { Transform } from '../util/Transform';
import { UndoManager, undoBatch } from '../util/UndoManager';
import { ObservableReactComponent } from './ObservableReactComponent';
import './PreviewCursor.scss';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';

@observer
export class PreviewCursor extends ObservableReactComponent<{}> {
    static _instance: PreviewCursor;
    public static get Instance() {
        return PreviewCursor._instance;
    }

    _onKeyPress?: (e: KeyboardEvent) => void;
    _getTransform?: () => Transform;
    _addDocument?: (doc: Doc | Doc[]) => boolean;
    _addLiveTextDoc?: (doc: Doc) => void;
    _nudge?: undefined | ((x: number, y: number) => boolean);
    _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
    @observable _clickPoint: number[] = [];
    @observable public Visible = false;
    public Doc: Opt<Doc>;
    constructor(props: any) {
        super(props);
        makeObservable(this);
        PreviewCursor._instance = this;
        this._clickPoint = observable([0, 0]);
        document.addEventListener('keydown', this.onKeyPress);
        document.addEventListener('paste', this.paste, true);
    }

    paste = async (e: ClipboardEvent) => {
        if (this.Visible && e.clipboardData) {
            const newPoint = this._getTransform?.().transformPoint(this._clickPoint[0], this._clickPoint[1]);
            runInAction(() => (this.Visible = false));

            // tests for URL and makes web document
            const re: any = /^https?:\/\//g;
            const plain = e.clipboardData.getData('text/plain');
            if (plain && newPoint) {
                // tests for youtube and makes video document
                if (plain.indexOf('www.youtube.com/watch') !== -1) {
                    const batch = UndoManager.StartBatch('youtube upload');
                    const generatedDocuments: Doc[] = [];
                    const options = {
                        title: plain,
                        _width: 400,
                        _height: 315,
                        x: newPoint[0],
                        y: newPoint[1],
                    };
                    this._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, this._addDocument ?? returnFalse).then(batch.end);
                } else if (re.test(plain)) {
                    const url = plain;
                    if (!url.startsWith(window.location.href)) {
                        undoBatch(() =>
                            this._addDocument?.(
                                Docs.Create.WebDocument(url, {
                                    title: url,
                                    _width: 500,
                                    _height: 300,
                                    data_useCors: true,
                                    x: newPoint[0],
                                    y: newPoint[1],
                                })
                            )
                        )();
                    } else alert('cannot paste dash into itself');
                } else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) {
                    const clone = plain.startsWith('__DashCloneId(');
                    const docids = plain.split(':');
                    const strs = docids[0].split(','); // hack!  docids[0] is the top left of the selection rectangle
                    const ptx = Number(strs[0].substring((clone ? '__DashCloneId(' : '__DashDocId(').length));
                    const pty = Number(strs[1].substring(0, strs[1].length - 1));
                    this._addDocument && Doc.Paste(docids.slice(1), clone, this._addDocument, ptx, pty, newPoint);

                    e.stopPropagation();
                } else {
                    FormattedTextBox.PasteOnLoad = e;
                    if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined)), 'paste');
                }
            }
            //pasting in images
            else if (e.clipboardData.getData('text/html') !== '' && e.clipboardData.getData('text/html').includes('<img src=')) {
                const re: any = /<img src="(.*?)"/g;
                const arr: any[] = re.exec(e.clipboardData.getData('text/html'));

                if (newPoint) {
                    undoBatch(() => {
                        const doc = Docs.Create.ImageDocument(arr[1], {
                            _width: 300,
                            title: arr[1],
                            x: newPoint[0],
                            y: newPoint[1],
                        });
                        ImageUtils.ExtractImgInfo(doc);
                        this._addDocument?.(doc);
                    })();
                }
            } else if (e.clipboardData.items.length && newPoint) {
                const batch = UndoManager.StartBatch('collection view drop');
                const files: File[] = [];
                Array.from(e.clipboardData.items).forEach(item => {
                    const file = item.getAsFile();
                    file && files.push(file);
                });
                const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] });
                this._addDocument && generatedDocuments.forEach(this._addDocument);
                batch.end();
            }
        }
    };

    @action
    onKeyPress = (e: KeyboardEvent) => {
        // Mixing events between React and Native is finicky.
        //if not these keys, make a textbox if preview cursor is active!
        if (
            e.key !== 'Escape' &&
            e.key !== 'Backspace' &&
            e.key !== 'Delete' &&
            e.key !== 'CapsLock' &&
            e.key !== 'Alt' &&
            e.key !== 'Shift' &&
            e.key !== 'Meta' &&
            e.key !== 'Control' &&
            e.key !== 'Insert' &&
            e.key !== 'Home' &&
            e.key !== 'End' &&
            e.key !== 'PageUp' &&
            e.key !== 'PageDown' &&
            e.key !== 'NumLock' &&
            e.key !== ' ' &&
            (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
            (e.keyCode < 173 || e.keyCode > 183 || e.key === '-') && // mute, volume up/down etc, - is there specifically because its keycode is 173 in Firefox so shouldn't be avoided
            !e.key.startsWith('Arrow') &&
            !e.defaultPrevented
        ) {
            if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {
                //  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
                this.Visible && this._onKeyPress?.(e);
                ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (this.Visible = false);
            }
        } else if (this.Visible) {
            if (e.key === 'ArrowRight') {
                this._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
            } else if (e.key === 'ArrowLeft') {
                this._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
            } else if (e.key === 'ArrowUp') {
                this._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
            } else if (e.key === 'ArrowDown') {
                this._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
            }
        }
    };

    //when focus is lost, this will remove the preview cursor
    @action onBlur = (): void => {
        this.Visible = false;
    };

    @action
    public static Show(
        x: number,
        y: number,
        onKeyPress: (e: KeyboardEvent) => void,
        addLiveText: (doc: Doc) => void,
        getTransform: () => Transform,
        addDocument: undefined | ((doc: Doc | Doc[]) => boolean),
        nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean),
        slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>
    ) {
        const self = PreviewCursor.Instance;
        if (self) {
            self._clickPoint = [x, y];
            self._onKeyPress = onKeyPress;
            self._addLiveTextDoc = addLiveText;
            self._getTransform = getTransform;
            self._addDocument = addDocument || returnFalse;
            self._nudge = nudge;
            self._slowLoadDocuments = slowLoadDocuments;
            self.Visible = true;
        }
    }
    render() {
        return !this._clickPoint || !this.Visible ? null : (
            <div className="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={e => e?.focus()} style={{ color: lightOrDark(this.Doc?.backgroundColor ?? 'white'), transform: `translate(${this._clickPoint[0]}px, ${this._clickPoint[1]}px)` }}>
                I
            </div>
        );
    }
}
