import _clone from 'lodash-es/clone';
import _extend from 'lodash-es/extend';
import _some from 'lodash-es/some';

import { dispatch as d3_dispatch } from 'd3-dispatch';

import {
    event as d3_event,
    select as d3_select
} from 'd3-selection';

import { t } from '../util/locale';
import { textDirection } from '../util/locale';
import { svgIcon } from '../svg';
import { uiFieldHelp } from './field_help';
import { uiFields } from './fields';
import { uiTagReference } from './tag_reference';
import { utilRebind } from '../util';


export function uiField(context, presetField, entity, options) {
    options = _extend({
        show: true,
        wrap: true,
        remove: true,
        revert: true,
        info: true
    }, options);

    var dispatch = d3_dispatch('change');
    var field = _clone(presetField);
    var _show = options.show;
    var _state = '';
    var _tags = {};

    field.keys = field.keys || [field.key];

    // only create the fields that are actually being shown
    if (_show && !field.impl) {
        createField();
    }

    // field implementation
    function createField() {
        field.impl = uiFields[field.type](field, context)
            .on('change', function(t, onInput) {
                dispatch.call('change', field, t, onInput);
            });

        // if this field cares about the entity, pass it along
        if (entity && field.impl.entity) {
            field.entityID = entity.id;
            field.impl.entity(entity);
        }
    }


    function isModified() {
        if (!entity) return false;
        var original = context.graph().base().entities[entity.id];
        return _some(field.keys, function(key) {
            return original ? _tags[key] !== original.tags[key] : _tags[key];
        });
    }


    function isPresent() {
        return _some(field.keys, function(key) {
            return _tags[key];
        });
    }


    function revert(d) {
        d3_event.stopPropagation();
        d3_event.preventDefault();
        if (!entity) return false;

        var original = context.graph().base().entities[entity.id];
        var t = {};
        d.keys.forEach(function(key) {
            t[key] = original ? original.tags[key] : undefined;
        });

        dispatch.call('change', d, t);
    }


    function remove(d) {
        d3_event.stopPropagation();
        d3_event.preventDefault();

        var t = {};
        d.keys.forEach(function(key) {
            t[key] = undefined;
        });

        dispatch.call('change', d, t);
    }


    field.render = function(selection) {
        var container = selection.selectAll('.form-field')
            .data([field]);

        // Enter
        var enter = container.enter()
            .append('div')
            .attr('class', function(d) { return 'form-field form-field-' + d.safeid; })
            .classed('nowrap', !options.wrap);

        if (options.wrap) {
            var label = enter
                .append('label')
                .attr('class', 'form-field-label')
                .attr('for', function(d) { return 'preset-input-' + d.safeid; });

            label
                .append('span')
                .attr('class', 'label-text')
                .text(function(d) { return d.label(); });

            if (options.remove) {
                label
                    .append('button')
                    .attr('class', 'remove-icon')
                    .attr('title', t('icons.remove'))
                    .attr('tabindex', -1)
                    .call(svgIcon('#iD-operation-delete'));
            }

            if (options.revert) {
                label
                    .append('button')
                    .attr('class', 'modified-icon')
                    .attr('title', t('icons.undo'))
                    .attr('tabindex', -1)
                    .call(svgIcon((textDirection === 'rtl') ? '#iD-icon-redo' : '#iD-icon-undo'));
            }
        }


        // Update
        container = container
            .merge(enter);

        container.select('.form-field-label > .remove-icon')  // propagate bound data
            .on('click', remove);

        container.select('.form-field-label > .modified-icon')  // propagate bound data
            .on('click', revert);

        container
            .classed('modified', isModified())
            .classed('present', isPresent())
            .each(function(d) {
                if (!d.impl) return;  // field is not yet shown

                var reference, help;

                // instantiate field help
                if (options.wrap && field.type === 'restrictions') {
                    help = uiFieldHelp(context, 'restrictions');
                }

                // instantiate tag reference
                if (options.wrap && options.info) {
                    var referenceKey = d.key;
                    if (d.type === 'multiCombo') {   // lookup key without the trailing ':'
                        referenceKey = referenceKey.replace(/:$/, '');
                    }

                    reference = uiTagReference(d.reference || { key: referenceKey }, context);
                    if (_state === 'hover') {
                        reference.showing(false);
                    }
                }

                d3_select(this)
                    .call(d.impl);

                // add field help components
                if (help) {
                    d3_select(this)
                        .call(help.body)
                        .select('.form-field-label')
                        .call(help.button);
                }

                // add tag reference components
                if (reference) {
                    d3_select(this)
                        .call(reference.body)
                        .select('.form-field-label')
                        .call(reference.button);
                }

                d.impl.tags(_tags);
            });
    };


    field.state = function(val) {
        if (!arguments.length) return _state;
        _state = val;
        return field;
    };


    field.tags = function(val) {
        if (!arguments.length) return _tags;
        _tags = val;
        return field;
    };


    field.show = function() {
        _show = true;
        if (!field.impl) {
            createField();
        }
        if (field.default && field.key && _tags[field.key] !== field.default) {
            var t = {};
            t[field.key] = field.default;
            dispatch.call('change', this, t);
        }
    };


    field.isShown = function() {
        return _show || _some(field.keys, function(key) { return !!_tags[key]; });
    };


    field.focus = function() {
        field.impl.focus();
    };


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

@@ -1072,28 +1072,98 @@
  // 
-field implementation
+Creates the field.. This is done lazily,%0A    // once we know that the field will be shown.
 %0A   
@@ -1949,32 +1949,46 @@
 eturn _tags%5Bkey%5D
+ !== undefined
 ;%0A        %7D);%0A  
@@ -4524,42 +4524,62 @@
 pl) 
-return;  // field is not yet shown
+%7B%0A                    createField();%0A                %7D
 %0A%0A  
@@ -6700,64 +6700,18 @@
  %7C%7C 
-_some(field.keys, function(key) %7B return !!_tags%5Bkey%5D; %7D
+isPresent(
 );%0A 
@@ -6741,32 +6741,62 @@
  = function() %7B%0A
+        if (field.impl) %7B%0A    
         field.im
@@ -6803,24 +6803,34 @@
 pl.focus();%0A
+        %7D%0A
     %7D;%0A%0A%0A   

Fix lazy instantiation of the fields They are shown either if `_show=true` or `isPresent()`