import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';

import { actionChangeTags } from '../../actions/change_tags';
import { services } from '../../services/index';
import { svgIcon } from '../../svg/icon';
import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util';
import { uiCombobox } from '../combobox';
import { t } from '../../core/localizer';


export function uiFieldWikidata(field, context) {
    var wikidata = services.wikidata;
    var dispatch = d3_dispatch('change');

    var _selection = d3_select(null);
    var _searchInput = d3_select(null);
    var _qid = null;
    var _wikidataEntity = null;
    var _wikiURL = '';
    var _entityIDs = [];

    var _wikipediaKey = field.keys && field.keys.find(function(key) {
        return key.includes('wikipedia');
    });
    var _hintKey = field.key === 'wikidata' ? 'name' : field.key.split(':')[0];

    var combobox = uiCombobox(context, 'combo-' + field.safeid)
        .caseSensitive(true)
        .minItems(1);


    function wiki(selection) {
        _selection = selection;

        var wrap = selection.selectAll('.form-field-input-wrap')
            .data([0]);

        wrap = wrap.enter()
            .append('div')
            .attr('class', 'form-field-input-wrap form-field-input-' + field.type)
            .merge(wrap);


        var list = wrap.selectAll('ul')
            .data([0]);

        list = list.enter()
            .append('ul')
            .attr('class', 'rows')
            .merge(list);

        var searchRow = list.selectAll('li.wikidata-search')
            .data([0]);

        var searchRowEnter = searchRow.enter()
            .append('li')
            .attr('class', 'wikidata-search');

        searchRowEnter
            .append('input')
            .attr('type', 'text')
            .attr('id', field.domId)
            .style('flex', '1')
            .call(utilNoAuto)
            .on('focus', function() {
                var node = d3_select(this).node();
                node.setSelectionRange(0, node.value.length);
            })
            .on('blur', function() {
                setLabelForEntity();
            })
            .call(combobox.fetcher(fetchWikidataItems));

        combobox.on('accept', function(d) {
            if (d) {
                _qid = d.id;
                change();
            }
        }).on('cancel', function() {
            setLabelForEntity();
        });

        searchRowEnter
            .append('button')
            .attr('class', 'form-field-button wiki-link')
            .attr('title', t('icons.view_on', { domain: 'wikidata.org' }))
            .call(svgIcon('#iD-icon-out-link'))
            .on('click', function(d3_event) {
                d3_event.preventDefault();
                if (_wikiURL) window.open(_wikiURL, '_blank');
            });

        searchRow = searchRow.merge(searchRowEnter);

        _searchInput = searchRow.select('input');

        var wikidataProperties = ['description', 'identifier'];

        var items = list.selectAll('li.labeled-input')
            .data(wikidataProperties);

        // Enter
        var enter = items.enter()
            .append('li')
            .attr('class', function(d) { return 'labeled-input preset-wikidata-' + d; });

        enter
            .append('div')
            .attr('class', 'label')
            .html(function(d) { return t.html('wikidata.' + d); });

        enter
            .append('input')
            .attr('type', 'text')
            .call(utilNoAuto)
            .classed('disabled', 'true')
            .attr('readonly', 'true');

        enter
            .append('button')
            .attr('class', 'form-field-button')
            .attr('title', t('icons.copy'))
            .call(svgIcon('#iD-operation-copy'))
            .on('click', function(d3_event) {
                d3_event.preventDefault();
                d3_select(this.parentNode)
                    .select('input')
                    .node()
                    .select();
                document.execCommand('copy');
            });

    }

    function fetchWikidataItems(q, callback) {
        if (!q && _hintKey) {
            // other tags may be good search terms
            for (var i in _entityIDs) {
                var entity = context.hasEntity(_entityIDs[i]);
                if (entity.tags[_hintKey]) {
                    q = entity.tags[_hintKey];
                    break;
                }
            }
        }

        wikidata.itemsForSearchQuery(q, function(err, data) {
            if (err) {
                if (err !== 'No query') console.error(err); // eslint-disable-line
                return;
            }

            var result = data.map(function (item) {
                return {
                    id: item.id,
                    value: item.display.label.value + ' (' + item.id + ')',
                    display: selection => selection.append('span')
                        .attr('class', 'localized-text')
                        .attr('lang', item.display.label.language)
                        .text(item.display.label.value),
                    title: item.display.description && item.display.description.value,
                    terms: item.aliases
                };
            });

            if (callback) callback(result);
        });
    }


    function change() {
        var syncTags = {};
        syncTags[field.key] = _qid;
        dispatch.call('change', this, syncTags);

        // attempt asynchronous update of wikidata tag..
        var initGraph = context.graph();
        var initEntityIDs = _entityIDs;

        wikidata.entityByQID(_qid, function(err, entity) {
            if (err) return;

            // If graph has changed, we can't apply this update.
            if (context.graph() !== initGraph) return;

            if (!entity.sitelinks) return;

            var langs = wikidata.languagesToQuery();
            // use the label and description languages as fallbacks
            ['labels', 'descriptions'].forEach(function(key) {
                if (!entity[key]) return;

                var valueLangs = Object.keys(entity[key]);
                if (valueLangs.length === 0) return;
                var valueLang = valueLangs[0];

                if (langs.indexOf(valueLang) === -1) {
                    langs.push(valueLang);
                }
            });

            var newWikipediaValue;

            if (_wikipediaKey) {
                var foundPreferred;
                for (var i in langs) {
                    var lang = langs[i];
                    var siteID = lang.replace('-', '_') + 'wiki';
                    if (entity.sitelinks[siteID]) {
                        foundPreferred = true;
                        newWikipediaValue = lang + ':' + entity.sitelinks[siteID].title;
                        // use the first match
                        break;
                    }
                }

                if (!foundPreferred) {
                    // No wikipedia sites available in the user's language or the fallback languages,
                    // default to any wikipedia sitelink

                    var wikiSiteKeys = Object.keys(entity.sitelinks).filter(function(site) {
                        return site.endsWith('wiki');
                    });

                    if (wikiSiteKeys.length === 0) {
                        // if no wikipedia pages are linked to this wikidata entity, delete that tag
                        newWikipediaValue = null;
                    } else {
                        var wikiLang = wikiSiteKeys[0].slice(0, -4).replace('_', '-');
                        var wikiTitle = entity.sitelinks[wikiSiteKeys[0]].title;
                        newWikipediaValue = wikiLang + ':' + wikiTitle;
                    }
                }
            }

            if (newWikipediaValue) {
                newWikipediaValue = context.cleanTagValue(newWikipediaValue);
            }

            if (typeof newWikipediaValue === 'undefined') return;

            var actions = initEntityIDs.map(function(entityID) {
                var entity = context.hasEntity(entityID);
                if (!entity) return null;

                var currTags = Object.assign({}, entity.tags);  // shallow copy
                if (newWikipediaValue === null) {
                    if (!currTags[_wikipediaKey]) return null;

                    delete currTags[_wikipediaKey];
                } else {
                    currTags[_wikipediaKey] = newWikipediaValue;
                }

                return actionChangeTags(entityID, currTags);
            }).filter(Boolean);

            if (!actions.length) return;

            // Coalesce the update of wikidata tag into the previous tag change
            context.overwrite(
                function actionUpdateWikipediaTags(graph) {
                    actions.forEach(function(action) {
                        graph = action(graph);
                    });
                    return graph;
                },
                context.history().undoAnnotation()
            );

            // do not dispatch.call('change') here, because entity_editor
            // changeTags() is not intended to be called asynchronously
        });
    }

    function setLabelForEntity() {
        var label = {
          value: ''
        };
        if (_wikidataEntity) {
            label = entityPropertyForDisplay(_wikidataEntity, 'labels');
            if (label.value.length === 0) {
                label.value = _wikidataEntity.id.toString();
            }
        }
        utilGetSetValue(_searchInput, label.value)
            .attr('lang', label.language);
    }


    wiki.tags = function(tags) {

        var isMixed = Array.isArray(tags[field.key]);
        _searchInput
            .attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : null)
            .attr('placeholder', isMixed ? t('inspector.multiple_values') : '')
            .classed('mixed', isMixed);

        _qid = typeof tags[field.key] === 'string' && tags[field.key] || '';

        if (!/^Q[0-9]*$/.test(_qid)) {   // not a proper QID
            unrecognized();
            return;
        }

        // QID value in correct format
        _wikiURL = 'https://wikidata.org/wiki/' + _qid;
        wikidata.entityByQID(_qid, function(err, entity) {
            if (err) {
                unrecognized();
                return;
            }
            _wikidataEntity = entity;

            setLabelForEntity();

            var description = entityPropertyForDisplay(entity, 'descriptions');

            _selection.select('button.wiki-link')
                .classed('disabled', false);

            _selection.select('.preset-wikidata-description')
                .style('display', function(){
                    return description.value.length > 0 ? 'flex' : 'none';
                })
                .select('input')
                .attr('value', description.value)
                .attr('lang', description.language);

            _selection.select('.preset-wikidata-identifier')
                .style('display', function(){
                    return entity.id ? 'flex' : 'none';
                })
                .select('input')
                .attr('value', entity.id);
        });


        // not a proper QID
        function unrecognized() {
            _wikidataEntity = null;
            setLabelForEntity();

            _selection.select('.preset-wikidata-description')
                .style('display', 'none');
            _selection.select('.preset-wikidata-identifier')
                .style('display', 'none');

            _selection.select('button.wiki-link')
                .classed('disabled', true);

            if (_qid && _qid !== '') {
                _wikiURL = 'https://wikidata.org/wiki/Special:Search?search=' + _qid;
            } else {
                _wikiURL = '';
            }
        }
    };

    function entityPropertyForDisplay(wikidataEntity, propKey) {
        var blankResponse = { value: '' };
        if (!wikidataEntity[propKey]) return blankResponse;
        var propObj = wikidataEntity[propKey];
        var langKeys = Object.keys(propObj);
        if (langKeys.length === 0) return blankResponse;
        // sorted by priority, since we want to show the user's language first if possible
        var langs = wikidata.languagesToQuery();
        for (var i in langs) {
            var lang = langs[i];
            var valueObj = propObj[lang];
            if (valueObj && valueObj.value && valueObj.value.length > 0) return valueObj;
        }
        // default to any available value
        return propObj[langKeys[0]];
    }


    wiki.entityIDs = function(val) {
        if (!arguments.length) return _entityIDs;
        _entityIDs = val;
        return wiki;
    };


    wiki.focus = function() {
        _searchInput.node().focus();
    };


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