import React from "react"
import { escape } from "lodash"
// based on https://raw.githubusercontent.com/lovasoa/react-contenteditable/master/src/react-contenteditable.js

export default class TextContentEditable extends React.Component {
    constructor(props) {
        super(props)
        // The following is weird, but currently necessary because of how
        // the div created on render() will call these. Thisception.
        this.emitChange = this.emitChange.bind(this)
        this.handleFocus = this.handleFocus.bind(this)
        this.handleBlur = this.handleBlur.bind(this)
        this.handleInput = this.handleInput.bind(this)
        this.onFilledChange = props.onFilledChange

        this.state = {
            isFilled: !!props.html
        }
    }

    render() {
        const { tagName, html, onFilledChange, ...props } = this.props

        return React.createElement(
            tagName || "div",
            {
                ...props,
                ref: e => (this.Elem = e),

                onInput: this.handleInput,
                onKeyUp: this.handleInput, // This is bc of IE11
                onFocus: this.handleFocus,
                onBlur: this.handleBlur,
                onKeyDown: this.props.onKeyDown || this.keyDown,
                contentEditable: !this.props.disabled,
                dangerouslySetInnerHTML: { __html: escape(html) }
            },
            this.props.children
        )
    }

    handleFocus(focusEvent) {
        const content = this.Elem.innerText
        this.setState({ isFilled: !!content })
    }

    handleBlur(blurEvent) {
        blurEvent.persist()
        const el = blurEvent.target
        const newContent = el.textContent
        if (this.props.onBlur) {
            this.props.onBlur(blurEvent)
        }

        this.setState(({ html }) => {
            if (html !== newContent) this.emitChange(blurEvent)
            return { html: newContent }
        })
    }

    handleInput(inputEvent) {
        const content = this.Elem.innerText
        this.setState(({ isFilled }) => {
            const newFilledState = !!content
            if (isFilled !== newFilledState) {
                // .target.value just to keep the same structure as a regular event
                this.onFilledChange({ target: { value: content } })
            }

            return {
                isFilled: newFilledState
            }
        })
    }

    toText(element) {
        // https://stackoverflow.com/questions/25892187/convert-innerhtml-of-a-contenteditable-text-to-normal-string
        const convertElement = function(element) {
            switch (element.tagName) {
                case "BR":
                    return " " // or should be \n ?
                case "P": // fall through to DIV
                case "DIV":
                    return (
                        (element.previousSibling ? "" : "") +
                        [].map.call(element.childNodes, convertElement).join("")
                    )
                default:
                    return element.textContent
            }
        }

        return [].map.call(element.childNodes, convertElement).join("")
    }

    shouldComponentUpdate(nextProps) {
        // We need not rerender if the change of props simply reflects the user's
        // edits. Rerendering in this case would make the cursor/caret jump.
        return (
            // Rerender if there is no element yet... (somehow?)
            !this.Elem ||
            // ...or if html really changed... (programmatically, not by user edit)
            (nextProps.html !== this.Elem.innerHTML &&
                nextProps.html !== this.props.html) ||
            // ...or if editing is enabled or disabled.
            this.props.disabled !== nextProps.disabled ||
            // ...or if className changed
            this.props.className !== nextProps.className
        )
    }

    componentDidUpdate() {
        if (this.Elem && this.props.html !== this.Elem.innerHTML) {
            // Perhaps React (whose VDOM gets outdated because we often prevent
            // rerendering) did not update the DOM. So we update it manually now.
            this.Elem.innerHTML = this.props.html || ""
        }
    }

    emitChange(evt) {
        if (!this.Elem) return
        var html = this.Elem.innerText
        if (this.props.onChange && html !== this.lastHtml) {
            // Original HTML evt.target = { value: html };
            evt.target = { value: this.toText(this.Elem) }
            this.props.onChange(evt)
        }
        this.lastHtml = html
    }

    keyDown(evt) {
        if (evt.which === 13) {
            evt.preventDefault()
        }
    }
}
