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

import { geoVecAdd } from '../geo';
import { localizer } from '../core/localizer';
import { uiTooltip } from './tooltip';
import { utilRebind } from '../util/rebind';
import { utilHighlightEntities } from '../util/util';
import { utilGetDimensions } from '../util/dimensions';
import { svgIcon } from '../svg/icon';


export function uiEditMenu(context) {
    var dispatch = d3_dispatch('toggled');

    var _menu = d3_select(null);
    var _operations = [];
    // the position the menu should be displayed relative to
    var _anchorLoc = [0, 0];
    var _anchorLocLonLat = [0, 0];
    // a string indicating how the menu was opened
    var _triggerType = '';

    var _vpTopMargin = 85; // viewport top margin
    var _vpBottomMargin = 45; // viewport bottom margin
    var _vpSideMargin = 35;   // viewport side margin

    var _menuTop = false;
    var _menuHeight;
    var _menuWidth;

    // hardcode these values to make menu positioning easier
    var _verticalPadding = 4;

    // see also `.edit-menu .tooltip` CSS; include margin
    var _tooltipWidth = 210;

    // offset the menu slightly from the target location
    var _menuSideMargin = 10;

    var _tooltips = [];

    var editMenu = function(selection) {

        var isTouchMenu = _triggerType.includes('touch') || _triggerType.includes('pen');

        var ops = _operations.filter(function(op) {
            return !isTouchMenu || !op.mouseOnly;
        });

        if (!ops.length) return;

        _tooltips = [];

        // Position the menu above the anchor for stylus and finger input
        // since the mapper's hand likely obscures the screen below the anchor
        _menuTop = isTouchMenu;

        // Show labels for touch input since there aren't hover tooltips
        var showLabels = isTouchMenu;

        var buttonHeight = showLabels ? 32 : 34;
        if (showLabels) {
            // Get a general idea of the width based on the length of the label
            _menuWidth = 52 + Math.min(120, 6 * Math.max.apply(Math, ops.map(function(op) {
                return op.title.length;
            })));
        } else {
            _menuWidth = 44;
        }

        _menuHeight = _verticalPadding * 2 + ops.length * buttonHeight;

        _menu = selection
            .append('div')
            .attr('class', 'edit-menu')
            .classed('touch-menu', isTouchMenu)
            .style('padding', _verticalPadding + 'px 0');

        var buttons = _menu.selectAll('.edit-menu-item')
            .data(ops);

        // enter
        var buttonsEnter = buttons.enter()
            .append('button')
            .attr('class', function (d) { return 'edit-menu-item edit-menu-item-' + d.id; })
            .style('height', buttonHeight + 'px')
            .on('click', click)
            // don't listen for `mouseup` because we only care about non-mouse pointer types
            .on('pointerup', pointerup)
            .on('pointerdown mousedown', function pointerdown(d3_event) {
                // don't let button presses also act as map input - #1869
                d3_event.stopPropagation();
            })
            .on('mouseenter.highlight', function(d3_event, d) {
                if (d3_select(this).classed('disabled')) return;

                if (d.relatedEntityIds) {
                    utilHighlightEntities(d.relatedEntityIds(), true, context);
                }

                if (d.getAuxiliaryGeometry) {
                    drawAuxiliaryGeometry(context, d.getAuxiliaryGeometry());
                }
            })
            .on('mouseleave.highlight', function(d3_event, d) {
                if (d.relatedEntityIds) {
                    utilHighlightEntities(d.relatedEntityIds(), false, context);
                }

                if (d.getAuxiliaryGeometry) {
                    drawAuxiliaryGeometry(context, []);
                }
            });

        buttonsEnter.each(function(d) {
            var tooltip = uiTooltip()
                .scrollContainer(context.container().select('.over-map'))
                .heading(() => d.title)
                .title(d.tooltip)
                .keys([d.keys[0]]);

            _tooltips.push(tooltip);

            d3_select(this)
                .call(tooltip)
                .append('div')
                .attr('class', 'icon-wrap')
                .call(svgIcon(d.icon && d.icon() || '#iD-operation-' + d.id, 'operation'));
        });

        if (showLabels) {
            buttonsEnter.append('span')
                .attr('class', 'label')
                .each(function(d) {
                    d3_select(this).call(d.title);
                });
        }

        // update
        buttonsEnter
            .merge(buttons)
            .classed('disabled', d => {
                // interruptible operations are not shown as disabled.
                const reason = d.disabled();
                return reason && !d.interrupts?.[reason];
            });

        updatePosition();

        var initialScale = context.projection.scale();
        context.map()
            .on('move.edit-menu', function() {
                if (initialScale !== context.projection.scale()) {
                    editMenu.close();
                }
            })
            .on('drawn.edit-menu', function(info) {
                if (info.full) updatePosition();
            });

        var lastPointerUpType;
        // `pointerup` is always called before `click`
        function pointerup(d3_event) {
            lastPointerUpType = d3_event.pointerType;
        }

        function click(d3_event, operation) {
            d3_event.stopPropagation();

            if (operation.relatedEntityIds) {
                utilHighlightEntities(operation.relatedEntityIds(), false, context);
            }

            const disabled = operation.disabled();
            if (disabled) {
                const interrupt = operation.interrupts?.[disabled];
                if (interrupt) {
                    interrupt();
                } else if (lastPointerUpType === 'touch' ||
                    lastPointerUpType === 'pen') {
                    // there are no tooltips for touch interactions so flash feedback instead
                    context.ui().flash
                        .duration(4000)
                        .iconName('#iD-operation-' + operation.id)
                        .iconClass('operation disabled')
                        .label(operation.tooltip())();
                }
            } else {
                if (lastPointerUpType === 'touch' ||
                    lastPointerUpType === 'pen') {
                    context.ui().flash
                        .duration(2000)
                        .iconName('#iD-operation-' + operation.id)
                        .iconClass('operation')
                        .label(operation.annotation() || operation.title)();
                }

                operation();
                editMenu.close();
            }
            lastPointerUpType = null;
        }

        dispatch.call('toggled', this, true);
    };

    function updatePosition() {

        if (!_menu || _menu.empty()) return;

        var anchorLoc = context.projection(_anchorLocLonLat);

        var viewport = context.surfaceRect();

        if (anchorLoc[0] < 0 ||
            anchorLoc[0] > viewport.width ||
            anchorLoc[1] < 0 ||
            anchorLoc[1] > viewport.height) {
            // close the menu if it's gone offscreen

            editMenu.close();
            return;
        }

        var menuLeft = displayOnLeft(viewport);

        var offset = [0, 0];

        offset[0] = menuLeft ? -1 * (_menuSideMargin + _menuWidth) : _menuSideMargin;

        if (_menuTop) {
            if (anchorLoc[1] - _menuHeight < _vpTopMargin) {
                // menu is near top viewport edge, shift downward
                offset[1] = -anchorLoc[1] + _vpTopMargin;
            } else {
                offset[1] = -_menuHeight;
            }
        } else {
            if (anchorLoc[1] + _menuHeight > (viewport.height - _vpBottomMargin)) {
                // menu is near bottom viewport edge, shift upwards
                offset[1] = -anchorLoc[1] - _menuHeight + viewport.height - _vpBottomMargin;
            } else {
                offset[1] = 0;
            }
        }

        var origin = geoVecAdd(anchorLoc, offset);
        // repositioning the menu to account for the top menu height
        var _verticalOffset = parseFloat(utilGetDimensions(d3_select('.top-toolbar-wrap'))[1]);
        origin[1] -= _verticalOffset;

        _menu
            .style('left', origin[0] + 'px')
            .style('top', origin[1] + 'px');

        var tooltipSide = tooltipPosition(viewport, menuLeft);
        _tooltips.forEach(function(tooltip) {
            tooltip.placement(tooltipSide);
        });

        function displayOnLeft(viewport) {
            if (localizer.textDirection() === 'ltr') {
                if ((anchorLoc[0] + _menuSideMargin + _menuWidth) > (viewport.width - _vpSideMargin)) {
                    // right menu would be too close to the right viewport edge, go left
                    return true;
                }
                // prefer right menu
                return false;

            } else { // rtl
                if ((anchorLoc[0] - _menuSideMargin - _menuWidth) < _vpSideMargin) {
                    // left menu would be too close to the left viewport edge, go right
                    return false;
                }
                // prefer left menu
                return true;
            }
        }

        function tooltipPosition(viewport, menuLeft) {
            if (localizer.textDirection() === 'ltr') {
                if (menuLeft) {
                    // if there's not room for a right-side menu then there definitely
                    // isn't room for right-side tooltips
                    return 'left';
                }
                if ((anchorLoc[0] + _menuSideMargin + _menuWidth + _tooltipWidth) > (viewport.width - _vpSideMargin)) {
                    // right tooltips would be too close to the right viewport edge, go left
                    return 'left';
                }
                // prefer right tooltips
                return 'right';

            } else { // rtl
                if (!menuLeft) {
                    return 'right';
                }
                if ((anchorLoc[0] - _menuSideMargin - _menuWidth - _tooltipWidth) < _vpSideMargin) {
                    // left tooltips would be too close to the left viewport edge, go right
                    return 'right';
                }
                // prefer left tooltips
                return 'left';
            }
        }
    }

    editMenu.close = function () {

        context.map()
            .on('move.edit-menu', null)
            .on('drawn.edit-menu', null);

        _menu.remove();
        _tooltips = [];

        // Clean up any auxiliary overlays
        drawAuxiliaryGeometry(context, []);

        dispatch.call('toggled', this, false);
    };

    editMenu.anchorLoc = function(val) {
        if (!arguments.length) return _anchorLoc;
        _anchorLoc = val;
        _anchorLocLonLat = context.projection.invert(_anchorLoc);
        return editMenu;
    };

    editMenu.triggerType = function(val) {
        if (!arguments.length) return _triggerType;
        _triggerType = val;
        return editMenu;
    };

    editMenu.operations = function(val) {
        if (!arguments.length) return _operations;
        _operations = val;
        return editMenu;
    };

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


// Helper function to draw/remove reflect axis overlay
function drawAuxiliaryGeometry(context, d) {
    const surface = context.surface();
    // Append to the OSM data layer to be in the same coordinate space as map features
    const container = surface.selectAll('.data-layer.osm .auxiliary');
    const paths = container.selectAll('path')
        .data(d, d => d.id);

    paths.exit().remove();
    const enter = paths.enter()
        .append('path');

    enter.merge(paths)
        .attr('class', d => d.klass)
        .attr('d', d => d.path);
}
