/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as Autosuggest from 'react-autosuggest';
import { ObjectField } from '../../fields/ObjectField';
import './EditableView.scss';
import { DocumentIconContainer } from './nodes/DocumentIcon';
import { FieldView, FieldViewProps } from './nodes/FieldView';
import { ObservableReactComponent } from './ObservableReactComponent';
import { OverlayView } from './OverlayView';
import { Padding } 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;
    fieldContents?: FieldViewProps;
    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

    schemaHeader?: boolean;
    onClick?: () => void;
    updateAlt?: (newAlt: string) => void;
    updateSearch?: (value: string) => void;
}

/**
 * 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 ObservableReactComponent<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);
        makeObservable(this);
        this._editing = !!this._props.editing;
        if (this._props.schemaHeader) this._editing = true;
    }

    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 }
        );
    }

    componentDidUpdate(prevProps: Readonly<EditableProps>) {
        super.componentDidUpdate(prevProps);
        if (this._editing && this._props.editing === false) {
            this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false);
        } else
            runInAction(() => {
                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 });
        }
        this._props.updateSearch && this._props.updateSearch(targVal);
    };

    @action
    onKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
        if (e.nativeEvent.defaultPrevented) return; // hack .. DashFieldView grabs native events, but react ignores stoppedPropagation and preventDefault, so we need to check it here
        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;
                }
            // eslint-disable-next-line no-fallthrough
            default:
                if (this._props.textCallback?.(e.key)) {
                    e.stopPropagation();
                    this._editing = false;
                    this._props.isEditingCallback?.(false);
                }
        }
    };

    @action
    onClick = (e?: React.MouseEvent) => {
        this._props.onClick && this._props.onClick();
        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);
            }
        }
    };

    @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
                // eslint-disable-next-line react/jsx-props-no-spreading
                {...this._props.autosuggestProps.autosuggestProps}
                inputProps={{
                    className: 'editableView-input',
                    onKeyDown: this.onKeyDown,
                    autoFocus: true,
                    // @ts-ignore
                    onBlur: e => this.finalizeEdit(e.currentTarget.value, false, true, false),
                    onPointerDown: this.stopPropagation,
                    onClick: this.stopPropagation,
                    onPointerUp: this.stopPropagation,
                    value: this._props.autosuggestProps.value,
                    // @ts-ignore
                    onChange: this._props.autosuggestProps.onChange,
                }}
            />
        ) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? (
            <input
                className="editableView-input" 
                ref={r => { this._inputref = r; }} // prettier-ignore
                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()}
                // eslint-disable-next-line jsx-a11y/no-autofocus
                autoFocus
                onChange={this.onChange}
                onKeyDown={this.onKeyDown}
                onPointerDown={this.stopPropagation}
                onClick={this.stopPropagation}
                onPointerUp={this.stopPropagation}
            />
        ) : (
            <textarea
                className="editableView-input"
                ref={r => { this._inputref = r; }} // prettier-ignore
                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()}
                // eslint-disable-next-line jsx-a11y/no-autofocus
                autoFocus
                onChange={this.onChange}
                onKeyDown={this.onKeyDown}
                onPointerDown={this.stopPropagation}
                onClick={this.stopPropagation}
                onPointerUp={this.stopPropagation}
            />
        );
    }

    staticDisplay = () => {
        let toDisplay;
        const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
        if (this._props.schemaHeader){
            toDisplay = <input className="editableView-input"
            value={gval}
            placeholder='Add key'
            readOnly
            style={{ display: this._props.display, overflow: 'auto', pointerEvents: 'none', fontSize: this._props.fontSize, width: '100%', margin: 0, background: this._props.background}}
            // eslint-disable-next-line jsx-a11y/no-autofocus
        />
        } else {
            toDisplay = (<span className='editableView-static'
            style={{
                fontStyle: this._props.fontStyle,
                fontSize: this._props.fontSize
            }}>
            {
                // eslint-disable-next-line react/jsx-props-no-spreading
                this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : ''
            }
        </span>)
        }

        return toDisplay;
    }

    render() {
        const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
        if ((this._editing && gval !== undefined)) {
            return this._props.sizeToContent ? (
                <div style={{ display: 'grid', minWidth: 100 }}>
                    <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{gval}</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,
                    width: '100%',
                    maxHeight: this._props.maxHeight,
                    fontStyle: this._props.fontStyle,
                    fontSize: this._props.fontSize,
                }}
                onClick={this.onClick}>
                {this.staticDisplay()}
            </div>
        );
    }
}
