import React from 'react';
import ReactDOM from 'react-dom';
import { Model, ModelListener } from 'front';
import './dropdown.less';

let dom;

if (typeof window !== 'undefined') {
    dom = require('dd/lib/dom').default;
}


/**\

    title: DefaultOptionModel
    category: model
    description:
        A base interface for what is required in an option.
        The following values are not allowed if used with a "native" dropdown:
        null, undefined, '' (empty string).

\**/
class DefaultOptionModel extends Model {
    constructor(data) {
        super({
            title: '',
            value: ''
        });

        this.fill(data);
    }
}

/**\

    title: DefaultOption
    category: component
    description:
        A base interface for what is required in
        an option view
    props:
        select[Function]:
            a callback for when the options is selected
        selected[Boolean]:
            whether the option is selected or not

\**/
class DefaultOption extends ModelListener {
    static defaultProps = {
        selected: false,
        model: DefaultOptionModel
    };

    onClick() {
        /* istanbul ignore else: no one cares */
        if (typeof this.props.select === 'function') {
            this.props.select();
        }
    }

    render() {
        let classer = 'option';

        if (this.props.selected) {
            classer += ' active';
        }

        return (
            <div className={ classer }
                tabIndex="-1"
                onClick={ this.onClick.bind(this) }>
                { this.model.title }
            </div>
        );
    }
}

/**\

    title: NativeOption
    category: component
    description:
        An option template for native dropdowns

\**/
class NativeOption extends ModelListener {
    static defaultProps = {
        model: DefaultOptionModel
    };

    render() {
        return (
            <option
                value={ this.model.value }>
                { this.model.title }
            </option>
        );
    }
}

/**\

    title: Dropdown
    category: component
    description:
        a dropdown selector with arbitrary divs for options
    props:
        placeholder[String]:
            What is displayed if there is no selection made
        template[Component]:
            The view for the options. Should either expose the same props as
            DefaultOption or extend it.
        items[Model]:
            An array of models for the available options. Should either expose
            the same props as DefaultOptionModel or extend it.
        value:
            A value that must match the value property of a model
            within items
        onSelect[Function]:
            A callback triggered by the selection of an option, passing
            the matching model as the only parameter
        onAdd[Object]:
            An object with a title and callback parameter. If defined,
            a new option with the title of onAdd.title will be added to
            the bottom of the list of options. When clicked, the
            onAdd.callback function will be invoked.
        error[String]: Field specific error text
        disabled[Boolean]:
            Disables the element. Cannot enable an empty dropdown.
        native[Boolean]:
            whether the select/options are custom or native

\**/
class Dropdown extends React.Component {
    static defaultProps = {
        placeholder: 'Choose',
        template: DefaultOption,
        items: []
    };

    state = {
        at: null,
        value: null,
        open: false, // NOTE(wezleytsai): this is unreliable with native
        disabled: false
    };

    constructor(props) {
        let ni;

        super(props);

        if (props.hasOwnProperty('value')) {
            for (ni = 0; ni < this.props.items.length; ni++) {
                if (this.props.items[ni].value === props.value) {
                    this.state.at = ni + 1;
                    this.state.value = props.value;
                    break;
                }
            }
        }

        if (props.native) {
            this.state.value = ''; // unselected state for native select
        }

        this.state.disabled = !(props.items.length || props.onAdd);

        if (props.disabled) {
            this.state.disabled = props.disabled;
        }
    }

    componentWillReceiveProps(props) {
        let ni;
        const state = {
            at: null,
            value: null
        };

        if (this.props.native || props.native) {
            state.value = ''; // unselected state for native select
        }

        for (ni = 0; ni < this.props.items.length; ni++) {
            if (this.props.items[ni].value === props.value) {
                state.at = ni + 1;
                state.value = props.value;
                break;
            }
        }

        state.disabled = !(props.items.length || props.onAdd);

        if (props.disabled) {
            state.disabled = props.disabled;
        }

        this.setState(state);
    }

    componentDidMount() {
        this.node = ReactDOM.findDOMNode(this);

        /* istanbul ignore next: untestable for now */
        this.boundToggle = function(evt) {
            if (!this.state.open) {
                return;
            }

            if (dom(evt.target).closest(dom(this.node))._len) {
                return;
            }

            this.toggle();
        }.bind(this);

        window.addEventListener('click', this.boundToggle);
    }

    componentWillUnmount() {
        /* istanbul ignore next: untestable for now */
        if (true) { // eslint-disable-line no-constant-condition
            window.removeEventListener('click', this.boundToggle);
            this.node = null;
            this.boundToggle = null;
        }
    }

    toggle() {
        if (!this.state.disabled) {
            this.setState({ open: !this.state.open });
        }
    }

    addToggle() {
        /* istanbul ignore else: no one cares */
        if (typeof this.props.onAdd.callback === 'function') {
            this.props.onAdd.callback();
        }

        this.toggle();
    }

    selectOption(idx) {
        if (!idx) {
            return;
        }

        /* istanbul ignore else: no one cares */
        if (typeof this.props.onSelect === 'function') {
            this.props.onSelect(this.props.items[idx - 1]);
        }

        this.setState({
            at: idx,
            value: this.props.items[idx - 1].value,
            open: false
        });
    }

    selectOptionNative(evt) {
        const idx = this.props.items.findIndex(function(item) {
            return `${item.value}` === evt.target.value;
        }) + 1;

        // this means the onAdd option was clicked
        if (!idx && this.props.onAdd) {
            if (typeof this.props.onAdd.callback === 'function') {
                this.props.onAdd.callback();
            }

            this.setState({
                at: null,
                value: this.state.value || '',
                open: false
            });

            return;
        }

        this.selectOption(idx);
    }

    onKeyDownSelect(evt) {
        /* istanbul ignore next: hard to test */
        if (evt.keyCode === 38 || evt.keyCode === 40) {
            this.toggle();
        }
    }

    onBlur() {
        this.setState({ open: false });
    }

    render() {
        const options = [],
            template = this.props.native ? NativeOption : this.props.template;
        let title = this.props.placeholder,
            classer = 'dropdown',
            component,
            errorTooltip;

        if (this.state.at) {
            title = this.props.items[this.state.at - 1].title;
        }

        // disable if manually set as disabled
        // or if there are no items or onAdd prop
        if (this.state.disabled ||
            !(this.props.items.length || this.props.onAdd)) {
            classer += ' disabled';
        }

        if (this.props.error) {
            classer += ' has-error';

            if (!this.state.open && !this.state.disabled) {
                errorTooltip = (
                    <div className="error-tooltip">
                        { this.props.error }
                    </div>
                );
            }
        }

        if (this.state.open && !this.state.disabled) {
            classer += ' active';
        }

        // dropdown options
        this.props.items.forEach((item, idx) => {
            options.push(React.createElement(template, {
                key: item.value,
                model: item,
                selected: (this.state.at === idx + 1),
                select: this.selectOption.bind(this, idx + 1)
            }));
        });

        // onAdd option
        if (this.props.onAdd) {
            options.push(React.createElement(template, {
                key: -1,
                model: {
                    title: this.props.onAdd.title || 'Add New',
                    value: undefined
                },
                select: this.addToggle.bind(this),
                onChange: this.addToggle.bind(this)
            }));
        }

        // main dropdown component
        component = (
            <div className="container">
                <div className="header"
                    tabIndex={ this.state.disabled ? null : 0 }
                    onClick={ this.toggle.bind(this) }>
                    { title }
                </div>
                <div className="options">{ options }</div>
                { errorTooltip }
            </div>
        );

        // if native, use select/option tags
        if (this.props.native) {
            classer += ' native';
            component = (
                <div className="container">
                    <select className="header"
                        value={ this.state.value }
                        disabled={ this.state.disabled }
                        onKeyDown={ this.onKeyDownSelect.bind(this) }
                        onChange={ this.selectOptionNative.bind(this) }>
                        <option value=""
                            disabled
                            hidden>
                            { this.props.placeholder }
                        </option>
                        { options }
                    </select>
                    { errorTooltip }
                </div>
            );
        }

        return (
            <div className={ classer }>
                { component }
            </div>
        );
    }
}

export default Dropdown;

export { DefaultOption, DefaultOptionModel };
