import * as d3 from 'd3';
import { utilRebind } from '../../modules/util/rebind';
import { utilTriggerEvent } from '../../modules/util/trigger_event';


export function d3combobox() {
    var dispatch = d3.dispatch('accept'),
        container = d3.select(document.body),
        data = [],
        suggestions = [],
        minItems = 2,
        caseSensitive = false;

    var fetcher = function(val, cb) {
        cb(data.filter(function(d) {
            return d.value
                .toString()
                .toLowerCase()
                .indexOf(val.toLowerCase()) !== -1;
        }));
    };

    var combobox = function(input, attachTo) {
        var idx = -1,
            wrapper = container
                .selectAll('div.combobox')
                .filter(function(d) { return d === input.node(); }),
            shown = !wrapper.empty();

        input
            .classed('combobox-input', true)
            .on('focus.typeahead', focus)
            .on('blur.typeahead', blur)
            .on('keydown.typeahead', keydown)
            .on('keyup.typeahead', keyup)
            .on('input.typeahead', change)
            .each(function() {
                var parent = this.parentNode,
                    sibling = this.nextSibling;

                var caret = d3.select(parent).selectAll('.combobox-caret')
                    .filter(function(d) { return d === input.node(); })
                    .data([input.node()]);

                caret = caret.enter()
                  .insert('div', function() { return sibling; })
                    .attr('class', 'combobox-caret')
                  .merge(caret);

                caret
                    .on('mousedown', function () {
                        // prevent the form element from blurring. it blurs
                        // on mousedown
                        d3.event.stopPropagation();
                        d3.event.preventDefault();
                        if (!shown) {
                            input.node().focus();
                            fetch('', render);
                        } else {
                            hide();
                        }
                    });
            });

        function focus() {
            fetch(value(), render);
        }

        function blur() {
            window.setTimeout(hide, 150);
        }

        function show() {
            if (!shown) {
                wrapper = container
                    .insert('div', ':first-child')
                    .datum(input.node())
                    .attr('class', 'combobox')
                    .style('position', 'absolute')
                    .style('display', 'block')
                    .style('left', '0px')
                    .on('mousedown', function () {
                        // prevent moving focus out of the text field
                        d3.event.preventDefault();
                    });

                d3.select('body')
                    .on('scroll.combobox', render, true);

                shown = true;
            }
        }

        function hide() {
            if (shown) {
                idx = -1;
                wrapper.remove();

                d3.select('body')
                    .on('scroll.combobox', null);

                shown = false;
            }
        }

        function keydown() {
           switch (d3.event.keyCode) {
               // backspace, delete
               case 8:
               case 46:
                   input.on('input.typeahead', function() {
                       idx = -1;
                       render();
                       var start = input.property('selectionStart');
                       input.node().setSelectionRange(start, start);
                       input.on('input.typeahead', change);
                   });
                   break;
               // tab
               case 9:
                   wrapper.selectAll('a.selected').each(function (d) {
                       dispatch.call('accept', this, d);
                   });
                   hide();
                   break;
               // return
               case 13:
                   d3.event.preventDefault();
                   break;
               // up arrow
               case 38:
                   nav(-1);
                   d3.event.preventDefault();
                   break;
               // down arrow
               case 40:
                   nav(+1);
                   d3.event.preventDefault();
                   break;
           }
           d3.event.stopPropagation();
        }

        function keyup() {
            switch (d3.event.keyCode) {
                // escape
                case 27:
                    hide();
                    break;
                // return
                case 13:
                    wrapper.selectAll('a.selected').each(function (d) {
                       dispatch.call('accept', this, d);
                    });
                    hide();
                    break;
            }
        }

        function change() {
            fetch(value(), function() {
                if (input.property('selectionEnd') === input.property('value').length) {
                    autocomplete();
                }
                render();
            });
        }

        function nav(dir) {
            if (!suggestions.length) return;
            idx = Math.max(Math.min(idx + dir, suggestions.length - 1), 0);
            input.property('value', suggestions[idx].value);
            render();
            ensureVisible();
        }

        function value() {
            var value = input.property('value'),
                start = input.property('selectionStart'),
                end = input.property('selectionEnd');

            if (start && end) {
                value = value.substring(0, start);
            }

            return value;
        }

        function fetch(v, cb) {
            fetcher.call(input, v, function(_) {
                suggestions = _;
                cb();
            });
        }

        function autocomplete() {
            var v = caseSensitive ? value() : value().toLowerCase();
            idx = -1;
            if (!v) return;

            for (var i = 0; i < suggestions.length; i++) {
                var suggestion = suggestions[i].value,
                    compare = caseSensitive ? suggestion : suggestion.toLowerCase();

                if (compare.indexOf(v) === 0) {
                    idx = i;
                    input.property('value', suggestion);
                    input.node().setSelectionRange(v.length, suggestion.length);
                    return;
                }
            }
        }

        function render() {
            if (suggestions.length >= minItems && document.activeElement === input.node()) {
                show();
            } else {
                hide();
                return;
            }

            var options = wrapper
                .selectAll('a.combobox-option')
                .data(suggestions, function(d) { return d.value; });

            options.exit()
                .remove();

            options.enter()
                .append('a')
                .attr('class', 'combobox-option')
                .text(function(d) { return d.value; })
                .merge(options)
                .attr('title', function(d) { return d.title; })
                .classed('selected', function(d, i) { return i === idx; })
                .on('mouseover', select)
                .on('click', accept)
                .order();


            var node = attachTo ? attachTo.node() : input.node(),
                rect = node.getBoundingClientRect();

            wrapper
                .style('left', rect.left + 'px')
                .style('width', rect.width + 'px')
                .style('top', rect.height + rect.top + 'px');
        }

        function select(d, i) {
            idx = i;
            render();
        }

        function ensureVisible() {
            var node = wrapper.selectAll('a.selected').node();
            if (node) node.scrollIntoView();
        }

        function accept(d) {
            if (!shown) return;
            input.property('value', d.value);
            utilTriggerEvent(input, 'change');
            dispatch.call('accept', this, d);
            hide();
        }
    };

    combobox.fetcher = function(_) {
        if (!arguments.length) return fetcher;
        fetcher = _;
        return combobox;
    };

    combobox.data = function(_) {
        if (!arguments.length) return data;
        data = _;
        return combobox;
    };

    combobox.minItems = function(_) {
        if (!arguments.length) return minItems;
        minItems = _;
        return combobox;
    };

    combobox.caseSensitive = function(_) {
        if (!arguments.length) return caseSensitive;
        caseSensitive = _;
        return combobox;
    };

    combobox.container = function(_) {
        if (!arguments.length) return container;
        container = _;
        return combobox;
    };

    return utilRebind(combobox, dispatch, 'on');
}


d3combobox.off = function(input) {
    input
        .on('focus.typeahead', null)
        .on('blur.typeahead', null)
        .on('keydown.typeahead', null)
        .on('keyup.typeahead', null)
        .on('input.typeahead', null)
        .each(function() {
            d3.select(this.parentNode).selectAll('.combobox-caret')
                .filter(function(d) { return d === input.node(); })
                .on('mousedown', null);
        });

    d3.select('body')
        .on('scroll.combobox', null);
};
