import React, {Component} from 'react'
import classnames from 'classnames'

export interface ValueEditorProps {
    content: string
    onEditDone: (newVal: string) => void
    onEditStart?: () => void
    className?: string
    onInput?: (newVal: string) => void
    onKeyDown?: (e: React.KeyboardEvent<HTMLSpanElement>) => void
    placeholder?: string
}

/** "plaintext-only" for browsers which support it, "true" for everyone else */
const plaintextOnly: string = (() => {
    let div = document.createElement("div");
    div.setAttribute("contenteditable", "PLAINTEXT-ONLY");
    return div.contentEditable === "plaintext-only" ? "plaintext-only" : "true";
})();

const EVENT_DEBUG = false;

export default class ValueEditor extends Component<ValueEditorProps> {

    input = React.createRef<HTMLSpanElement>();

    render() {
        const className = classnames(
            "inline-input",
            this.props.className
        )

        return (
            <span
                ref={this.input}
                tabIndex={0}
                className={className}
                placeholder={this.props.placeholder}
                onFocus={this.onFocus}
                onBlur={this.onBlur}
                onKeyDown={this.onKeyDown}
                onInput={this.onInput}
                onPaste={this.onPaste}
                onMouseDown={this.onMouseDown}
                onClick={this.onClick}
            >{this.props.content}</span>
        )
    }

    componentDidUpdate(prevProps: Readonly<ValueEditorProps>) {
        if (prevProps.content !== this.props.content)
            this.props.onInput?.(this.props.content);
    }

    isEditing = (): boolean => {
        return this.input.current?.contentEditable === plaintextOnly;
    }

    startEditing = () => {
        if (!this.input.current) return console.error("unreachable");
        if (this.isEditing())
            return

        // For Firefox, we need to blur() and then focus() with a pause in between,
        // otherwise we run into a bunch of weird bugs.
        this.suppress_events = true;
        this.input.current.blur();
        this.input.current.contentEditable = plaintextOnly;
        window.requestAnimationFrame(() => {
            if (!this.input.current)
                return
            this.input.current.focus();
            this.suppress_events = false;

            const range = document.createRange();
            range.selectNodeContents(this.input.current);
            const sel = window.getSelection();
            sel?.removeAllRanges();
            sel?.addRange(range);

            this.props.onEditStart?.();
        });

    }
    resetValue = () => {
        if (!this.input.current) return console.error("unreachable");

        this.input.current.textContent = this.props.content;
        this.props.onInput?.(this.props.content);
    }
    finishEditing = () => {
        if (!this.input.current) return console.error("unreachable");

        this.props.onEditDone(this.input.current.textContent || "")

        this.input.current.blur()
        this.input.current.contentEditable = "inherit";
    }

    onPaste = (e: React.ClipboardEvent<HTMLSpanElement>) => {
        e.preventDefault()
        let content = e.clipboardData.getData("text/plain")
        document.execCommand("insertHTML", false, content)
    }

    /*
    We can't always keep inputs as contenteditable as that breaks text selection big time.
    As such, we do a fairly elaborate dance here to determine if we want to start editing or not.
    The current heuristic is similar to what the Chrome devtools do; we start editing if the user clicks
    and then does not select any content. We also handle focus events, but only if the focus is caused by
    keyboard navigation.
     */
    private suppress_events = false;
    onMouseDown = (e: React.MouseEvent) => {
        EVENT_DEBUG && console.debug("onMouseDown", this.suppress_events)
        this.suppress_events = true;
        window.addEventListener("mouseup", this.onMouseUp, {once: true});
    }

    onMouseUp = (e: MouseEvent) => {
        const still_on_elem = e.target === this.input.current;
        const has_not_selected_text = !window.getSelection()?.toString();

        EVENT_DEBUG && console.warn("mouseUp", this.suppress_events, still_on_elem, has_not_selected_text);

        if (still_on_elem && has_not_selected_text) {
            this.startEditing();
        }
        this.suppress_events = false;
    }

    onClick = (e: React.MouseEvent) => {
        EVENT_DEBUG && console.debug("onClick", this.suppress_events)
    }

    onFocus = (e: React.FocusEvent) => {
        EVENT_DEBUG && console.debug("onFocus", this.props.content, this.suppress_events)
        if (!this.input.current) throw "unreachable";
        if (this.suppress_events)
            return
        this.startEditing();
    }

    onInput = (e: React.FormEvent) => {
        this.props.onInput?.(this.input.current?.textContent || "")
    }

    onBlur = (e: React.FocusEvent<HTMLSpanElement>) => {
        EVENT_DEBUG && console.debug("onBlur", this.props.content, this.suppress_events)
        if (this.suppress_events)
            return
        this.finishEditing();
    }

    onKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
        EVENT_DEBUG && console.debug("keydown", e)
        e.stopPropagation()
        switch (e.key) {
            case "Escape":
                e.preventDefault()
                this.resetValue()
                this.finishEditing()
                break
            case "Enter":
                if (!e.shiftKey) {
                    e.preventDefault()
                    this.finishEditing()
                }
                break
            default:
                break
        }
        this.props.onKeyDown?.(e);
    }
}
