import React = require('react');
import { action, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as Autosuggest from 'react-autosuggest';
import { ObjectField } from '../../fields/ObjectField';
import './EditableView.scss';
import { DocumentIconContainer } from './nodes/DocumentIcon';
import { OverlayView } from './OverlayView';
import { EditableText } from 'browndash-components';

export interface EditableProps {
    /**
     * Called to get the initial value for editing
     *  */
    GetValue(): string | undefined;
    /**
     * Called to apply changes
     * @param value - The string entered by the user to set the value to
     * @returns `true` if setting the value was successful, `false` otherwise
     *  */
    SetValue(value: string, shiftDown?: boolean, enterKey?: boolean): boolean;
    OnFillDown?(value: string): void;
    OnTab?(shift?: boolean): void;
    OnEmpty?(): void;

    /**
     * The contents to render when not editing
     */
    contents: any;
    fontStyle?: string;
    fontSize?: number;
    height?: number | 'auto';
    sizeToContent?: boolean;
    maxHeight?: number;
    display?: string;
    overflow?: string;
    autosuggestProps?: {
        resetValue: () => void;
        value: string;
        onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void;
        autosuggestProps: Autosuggest.AutosuggestProps<string, any>;
    };
    oneLine?: boolean; // whether to display the editable view as a single input line or as a textarea
    allowCRs?: boolean; // can carriage returns be entered
    editing?: boolean;
    isEditingCallback?: (isEditing: boolean) => void;
    menuCallback?: (x: number, y: number) => void;
    textCallback?: (char: string) => boolean;
    showMenuOnLoad?: boolean;
    background?: string | undefined;
    placeholder?: string;
    wrap?: string; // nowrap, pre-wrap, etc
}

/**
 * Customizable view that can be given an arbitrary view to render normally,
 * but can also be edited with customizable functions to get a string version
 * of the content, and set the value based on the entered string.
 */
@observer
export class EditableView extends React.Component<EditableProps> {
    private _ref = React.createRef<HTMLDivElement>();
    private _inputref: HTMLInputElement | HTMLTextAreaElement | null = null;
    _overlayDisposer?: () => void;
    _editingDisposer?: IReactionDisposer;
    @observable _editing: boolean = false;

    constructor(props: EditableProps) {
        super(props);
        this._editing = this.props.editing ? true : false;
    }

    componentDidMount(): void {
        this._editingDisposer = reaction(
            () => this._editing,
            editing => {
                if (editing) {
                    setTimeout(() => {
                        if (this._inputref?.value.startsWith('=') || this._inputref?.value.startsWith(':=')) {
                            this._overlayDisposer?.();
                            this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
                        }
                    });
                } else {
                    this._overlayDisposer?.();
                    this._overlayDisposer = undefined;
                }
            },
            { fireImmediately: true }
        );
    }

    @action
    componentDidUpdate() {
        if (this._editing && this.props.editing === false) {
            this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false);
        } else if (this.props.editing !== undefined) {
            this._editing = this.props.editing;
        }
    }

    componentWillUnmount() {
        this._overlayDisposer?.();
        this._editingDisposer?.();
        this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false);
    }

    onChange = (e: React.ChangeEvent) => {
        const targVal = (e.target as any).value;
        if (!(targVal.startsWith(':=') || targVal.startsWith('='))) {
            this._overlayDisposer?.();
            this._overlayDisposer = undefined;
        } else if (!this._overlayDisposer) {
            this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
        }
    };

    @action
    onKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
        switch (e.key) {
            case 'Tab':
                e.stopPropagation();
                this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, false);
                this.props.OnTab?.(e.shiftKey);
                break;
            case 'Backspace':
                e.stopPropagation();
                if (!e.currentTarget.value) this.props.OnEmpty?.();
                break;
            case 'Enter':
                if (this.props.allowCRs !== true) {
                    e.stopPropagation();
                    if (!e.ctrlKey) {
                        this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true);
                    } else if (this.props.OnFillDown) {
                        this.props.OnFillDown(e.currentTarget.value);
                        this._editing = false;
                        this.props.isEditingCallback?.(false);
                    }
                }
                break;
            case 'Escape':
                e.stopPropagation();
                this._editing = false;
                this.props.isEditingCallback?.(false);
                break;
            case 'ArrowUp':
            case 'ArrowDown':
            case 'ArrowLeft':
            case 'ArrowRight':
                e.stopPropagation();
                break;
            case 'Shift':
            case 'Alt':
            case 'Meta':
            case 'Control':
                break;
            case ':':
                if (this.props.menuCallback) {
                    e.stopPropagation();
                    this.props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
                    break;
                }

            default:
                if (this.props.textCallback?.(e.key)) {
                    e.stopPropagation();
                    this._editing = false;
                    this.props.isEditingCallback?.(false);
                }
        }
    };

    @action
    onClick = (e: React.MouseEvent) => {
        if (this.props.editing !== false) {
            e.nativeEvent.stopPropagation();
            if (this._ref.current && this.props.showMenuOnLoad) {
                this.props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
            } else {
                this._editing = true;
                this.props.isEditingCallback?.(true);
            }
            // e.stopPropagation();
        }
    };

    @action
    finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) {
        if (this.props.SetValue(value, shiftDown, enterKey)) {
            this._editing = false;
            this.props.isEditingCallback?.(false);
        } else {
            this._editing = false;
            this.props.isEditingCallback?.(false);
            !lostFocus &&
                setTimeout(
                    action(() => {
                        this._editing = true;
                        this.props.isEditingCallback?.(true);
                    }),
                    0
                );
        }
    }

    stopPropagation(e: React.SyntheticEvent) {
        e.stopPropagation();
    }

    @action
    setIsFocused = (value: boolean) => {
        const wasFocused = this._editing;
        this._editing = value;
        return wasFocused !== this._editing;
    };

    renderEditor() {
        return this.props.autosuggestProps ? (
            <Autosuggest
                {...this.props.autosuggestProps.autosuggestProps}
                inputProps={{
                    className: 'editableView-input',
                    onKeyDown: this.onKeyDown,
                    autoFocus: true,
                    onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true, false),
                    onPointerDown: this.stopPropagation,
                    onClick: this.stopPropagation,
                    onPointerUp: this.stopPropagation,
                    onKeyPress: this.stopPropagation,
                    value: this.props.autosuggestProps.value,
                    onChange: this.props.autosuggestProps.onChange,
                }}
            />
        ) : <EditableText
                // oneLine={this.props.oneLine && this.props.GetValue()?.toString().indexOf('\n') === -1}
                editing={true} 
                onEdit={function (newText: string): void {
                    throw new Error('Function not implemented.');
                } } 
                setEditing={function (editing: boolean): void {
                    throw new Error('Function not implemented.');
                } }                
                // onEdit={this.onChange}
            />
        //     // <input
        //     //     className="editableView-input"
        //     //     ref={r => (this._inputref = r)}
        //     //     style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }}
        //     //     placeholder={this.props.placeholder}
        //     //     onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
        //     //     defaultValue={this.props.GetValue()}
        //     //     autoFocus={true}
        //     //     onChange={this.onChange}
        //     //     onKeyDown={this.onKeyDown}
        //     //     onKeyPress={this.stopPropagation}
        //     //     onPointerDown={this.stopPropagation}
        //     //     onClick={this.stopPropagation}
        //     //     onPointerUp={this.stopPropagation}
        //     // />
        // ) : (
        //     <textarea
        //         className="editableView-input"
        //         ref={r => (this._inputref = r)}
        //         style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this.props.background }}
        //         placeholder={this.props.placeholder}
        //         onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
        //         defaultValue={this.props.GetValue()}
        //         autoFocus={true}
        //         onChange={this.onChange}
        //         onKeyDown={this.onKeyDown}
        //         onKeyPress={this.stopPropagation}
        //         onPointerDown={this.stopPropagation}
        //         onClick={this.stopPropagation}
        //         onPointerUp={this.stopPropagation}
        //     />
        // );
    }

    render() {
        if (this._editing && this.props.GetValue() !== undefined) {
            return this.props.sizeToContent ? (
                <div style={{ display: 'grid', minWidth: 100 }}>
                    <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{this.props.GetValue()}</div>
                    {this.renderEditor()}
                </div>
            ) : (
                this.renderEditor()
            );
        }
        setTimeout(() => this.props.autosuggestProps?.resetValue());
        return this.props.contents instanceof ObjectField ? null : (
            <div
                className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`}
                ref={this._ref}
                style={{ display: this.props.display, textOverflow: this.props.overflow, minHeight: '10px', whiteSpace: this.props.oneLine ? 'nowrap' : 'pre-line', height: this.props.height, maxHeight: this.props.maxHeight }}
                //onPointerDown={this.stopPropagation}
                onClick={this.onClick}
                placeholder={this.props.placeholder}>
                <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
            </div>
        );
    }
}
