import _throttle from 'lodash-es/throttle';

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

import { geoSphericalDistance } from '../geo';
import { modeBrowse } from '../modes/browse';
import { modeSelect, modeSelectNote } from '../modes';
import { utilObjectOmit, utilQsString, utilStringQs } from '../util';
import { utilArrayIdentical } from '../util/array';
import { utilDisplayLabel } from '../util/utilDisplayLabel';
import { t } from '../core/localizer';
import { prefs } from '../core/preferences';


export function behaviorHash(context) {

    // cached window.location.hash
    var _cachedHash = null;
    // allowable latitude range
    var _latitudeLimit = 90 - 1e-8;

    function computedHashParameters() {
        var map = context.map();
        var center = map.center();
        var zoom = map.zoom();
        var precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
        var oldParams = utilObjectOmit(utilStringQs(window.location.hash),
            ['comment', 'source', 'hashtags', 'walkthrough']
        );
        var newParams = {};

        delete oldParams.id;
        var selected = context.selectedIDs().filter(function(id) {
            return context.hasEntity(id);
        });
        if (selected.length) {
            newParams.id = selected.join(',');
        } else if (context.selectedNoteID()) {
            newParams.id = `note/${context.selectedNoteID()}`;
        }

        newParams.map = zoom.toFixed(2) +
            '/' + center[1].toFixed(precision) +
            '/' + center[0].toFixed(precision);

        return Object.assign(oldParams, newParams);
    }

    function computedHash() {
        return '#' + utilQsString(computedHashParameters(), true);
    }

    function computedTitle(includeChangeCount) {

        var baseTitle = context.documentTitleBase() || 'iD';
        var contextual;
        var changeCount;
        var titleID;

        var selected = context.selectedIDs().filter(function(id) {
            return context.hasEntity(id);
        });
        if (selected.length) {
            var firstLabel = utilDisplayLabel(context.entity(selected[0]), context.graph());
            if (selected.length > 1) {
                contextual = t('title.labeled_and_more', {
                    labeled: firstLabel,
                    count: selected.length - 1
                });
            } else {
                contextual = firstLabel;
            }
            titleID = 'context';
        }

        if (includeChangeCount) {
            changeCount = context.history().difference().summary().length;
            if (changeCount > 0) {
                titleID = contextual ? 'changes_context' : 'changes';
            }
        }

        if (titleID) {
            return t('title.format.' + titleID, {
                changes: changeCount,
                base: baseTitle,
                context: contextual
            });
        }

        return baseTitle;
    }

    function updateTitle(includeChangeCount) {
        if (!context.setsDocumentTitle()) return;

        var newTitle = computedTitle(includeChangeCount);
        if (document.title !== newTitle) {
            document.title = newTitle;
        }
    }

    function updateHashIfNeeded() {
        if (context.inIntro()) return;

        var latestHash = computedHash();
        if (_cachedHash !== latestHash) {
            _cachedHash = latestHash;

            // Update the URL hash without affecting the browser navigation stack,
            // though unavoidably creating a browser history entry
            window.history.replaceState(null, computedTitle(false /* includeChangeCount */), latestHash);

            // set the title we want displayed for the browser tab/window
            updateTitle(true /* includeChangeCount */);

            // save last used map location for future
            const q = utilStringQs(latestHash);
            if (q.map) {
                prefs('map-location', q.map);
            }
        }
    }

    var _throttledUpdate = _throttle(updateHashIfNeeded, 500);
    var _throttledUpdateTitle = _throttle(function() {
        updateTitle(true /* includeChangeCount */);
    }, 500);

    function hashchange() {

        // ignore spurious hashchange events
        if (window.location.hash === _cachedHash) return;

        _cachedHash = window.location.hash;

        var q = utilStringQs(_cachedHash);
        var mapArgs = (q.map || '').split('/').map(Number);

        if (mapArgs.length < 3 || mapArgs.some(isNaN)) {
            // replace bogus hash
            updateHashIfNeeded();

        } else {
            // don't update if the new hash already reflects the state of iD
            if (_cachedHash === computedHash()) return;

            var mode = context.mode();

            context.map().centerZoom([mapArgs[2], Math.min(_latitudeLimit, Math.max(-_latitudeLimit, mapArgs[1]))], mapArgs[0]);

            if (q.id && mode) {
                var ids = q.id.split(',').filter(function(id) {
                    return context.hasEntity(id) || id.startsWith('note/');
                });
                if (ids.length && ['browse', 'select-note', 'select'].includes(mode.id)) {
                    if (ids.length === 1 && ids[0].startsWith('note/')) {
                        context.enter(modeSelectNote(context, ids[0]));
                    } else if (!utilArrayIdentical(mode.selectedIDs(), ids)) {
                        context.enter(modeSelect(context, ids));
                    }
                    return;
                }
            }

            var center = context.map().center();
            var dist = geoSphericalDistance(center, [mapArgs[2], mapArgs[1]]);
            var maxdist = 500;

            // Don't allow the hash location to change too much while drawing
            // This can happen if the user accidentally hit the back button.  #3996
            if (mode && mode.id.match(/^draw/) !== null && dist > maxdist) {
                context.enter(modeBrowse(context));
                return;
            }
        }
    }

    function behavior() {
        context.map()
            .on('move.behaviorHash', _throttledUpdate);

        context.history()
            .on('change.behaviorHash', _throttledUpdateTitle);

        context
            .on('enter.behaviorHash', _throttledUpdate);

        d3_select(window)
            .on('hashchange.behaviorHash', hashchange);

        var q = utilStringQs(window.location.hash);

        if (q.id) {
            // targeting specific features: download, select, and zoom to them
            const selectIds = q.id.split(',');
            if (selectIds.length === 1 && selectIds[0].startsWith('note/')) {
                const noteId = selectIds[0].split('/')[1];
                context.zoomToNote(noteId, !q.map);
            } else {
                context.zoomToEntities(
                    // convert ids to short form id: node/123 -> n123
                    selectIds.map(id => id.replace(/([nwr])[^/]*\//, '$1')),
                    !q.map);
            }
        }

        if (q.walkthrough === 'true') {
            behavior.startWalkthrough = true;
        }

        if (q.map) {
            behavior.hadLocation = true;
        } else if (!q.id && prefs('map-location')) {
            // center map at last visited map location
            const mapArgs = prefs('map-location').split('/').map(Number);
            context.map().centerZoom([mapArgs[2], Math.min(_latitudeLimit, Math.max(-_latitudeLimit, mapArgs[1]))], mapArgs[0]);

            updateHashIfNeeded();

            behavior.hadLocation = true;
        }

        hashchange();

        updateTitle(false);
    }

    behavior.off = function() {
        _throttledUpdate.cancel();
        _throttledUpdateTitle.cancel();

        context.map()
            .on('move.behaviorHash', null);

        context
            .on('enter.behaviorHash', null);

        d3_select(window)
            .on('hashchange.behaviorHash', null);

        window.location.hash = '';
    };

    return behavior;
}
