"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module SelectionSet
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SelectionTool = exports.SelectionProcessing = exports.SelectionMode = exports.SelectionMethod = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const appui_abstract_1 = require("@itwin/appui-abstract");
const ElementLocateManager_1 = require("../ElementLocateManager");
const IModelApp_1 = require("../IModelApp");
const Pixel_1 = require("../render/Pixel");
const ViewRect_1 = require("../common/ViewRect");
const PrimitiveTool_1 = require("./PrimitiveTool");
const Tool_1 = require("./Tool");
const ToolAdmin_1 = require("./ToolAdmin");
const ToolAssistance_1 = require("./ToolAssistance");
// cSpell:ignore buttongroup
/** The method for choosing elements with the [[SelectionTool]]
 * @public
 * @extensions
 */
var SelectionMethod;
(function (SelectionMethod) {
    /** Identify element(s) by picking for drag selection (inside/overlap for drag box selection determined by point direction and shift key) */
    SelectionMethod[SelectionMethod["Pick"] = 0] = "Pick";
    /** Identify elements by overlap with crossing line */
    SelectionMethod[SelectionMethod["Line"] = 1] = "Line";
    /** Identify elements by box selection (inside/overlap for box selection determined by point direction and shift key) */
    SelectionMethod[SelectionMethod["Box"] = 2] = "Box";
})(SelectionMethod || (exports.SelectionMethod = SelectionMethod = {}));
/** The mode for choosing elements with the [[SelectionTool]]
 * @public
 * @extensions
 */
var SelectionMode;
(function (SelectionMode) {
    /** Identified elements replace the current selection set (use control key to add or remove) */
    SelectionMode[SelectionMode["Replace"] = 0] = "Replace";
    /** Identified elements are added to the current selection set */
    SelectionMode[SelectionMode["Add"] = 1] = "Add";
    /** Identified elements are removed from the current selection set */
    SelectionMode[SelectionMode["Remove"] = 2] = "Remove";
})(SelectionMode || (exports.SelectionMode = SelectionMode = {}));
/** The processing method to use to update the current selection.
 * @public
 * @extensions
 */
var SelectionProcessing;
(function (SelectionProcessing) {
    /** Add element to selection. */
    SelectionProcessing[SelectionProcessing["AddElementToSelection"] = 0] = "AddElementToSelection";
    /** Remove element from selection. */
    SelectionProcessing[SelectionProcessing["RemoveElementFromSelection"] = 1] = "RemoveElementFromSelection";
    /** If element is in selection remove it, else add it. */
    SelectionProcessing[SelectionProcessing["InvertElementInSelection"] = 2] = "InvertElementInSelection";
    /** Replace current selection with element. */
    SelectionProcessing[SelectionProcessing["ReplaceSelectionWithElement"] = 3] = "ReplaceSelectionWithElement";
})(SelectionProcessing || (exports.SelectionProcessing = SelectionProcessing = {}));
/** Tool for picking a set of elements of interest, selected by the user.
 * @public
 */
class SelectionTool extends PrimitiveTool_1.PrimitiveTool {
    constructor() {
        super(...arguments);
        this._isSelectByPoints = false;
        this._isSuspended = false;
        this._points = [];
        this._selectionMethodValue = { value: SelectionMethod.Pick };
        this._selectionModeValue = { value: SelectionMode.Replace };
    }
    requireWriteableTarget() { return false; }
    autoLockTarget() { } // NOTE: For selecting elements we only care about iModel, so don't lock target model automatically.
    wantSelectionClearOnMiss(_ev) { return SelectionMode.Replace === this.selectionMode; }
    wantEditManipulators() { return SelectionMethod.Pick === this.selectionMethod; }
    wantPickableDecorations() { return this.wantEditManipulators(); } // Allow pickable decorations selection to be independent of manipulators...
    wantToolSettings() { return true; }
    get selectionMethod() { return this._selectionMethodValue.value; }
    set selectionMethod(method) { this._selectionMethodValue.value = method; }
    get selectionMode() { return this._selectionModeValue.value; }
    set selectionMode(mode) { this._selectionModeValue.value = mode; }
    static methodsMessage(str) { return Tool_1.CoreTools.translate(`ElementSet.SelectionMethods.${str}`); }
    /* The property descriptions used to generate ToolSettings UI. */
    static _getMethodsDescription() {
        return {
            name: SelectionTool._methodsName,
            displayLabel: "",
            typename: "enum",
            editor: {
                name: "enum-buttongroup",
                params: [{
                        type: appui_abstract_1.PropertyEditorParamTypes.ButtonGroupData,
                        buttons: [
                            { iconSpec: "icon-select-single" },
                            { iconSpec: "icon-select-line" },
                            { iconSpec: "icon-select-box" },
                        ],
                    }, {
                        type: appui_abstract_1.PropertyEditorParamTypes.SuppressEditorLabel,
                        suppressLabelPlaceholder: true,
                    },
                ],
            },
            enum: {
                choices: [
                    { label: SelectionTool.methodsMessage("Pick"), value: SelectionMethod.Pick },
                    { label: SelectionTool.methodsMessage("Line"), value: SelectionMethod.Line },
                    { label: SelectionTool.methodsMessage("Box"), value: SelectionMethod.Box },
                ],
            },
        };
    }
    static modesMessage(str) { return Tool_1.CoreTools.translate(`ElementSet.SelectionModes.${str}`); }
    /* The property descriptions used to generate ToolSettings UI. */
    static _getModesDescription() {
        return {
            name: SelectionTool._modesName,
            displayLabel: "",
            typename: "enum",
            editor: {
                name: "enum-buttongroup",
                params: [{
                        type: appui_abstract_1.PropertyEditorParamTypes.ButtonGroupData,
                        buttons: [
                            { iconSpec: "icon-replace" },
                            { iconSpec: "icon-select-plus" },
                            {
                                iconSpec: "icon-select-minus",
                                isEnabledFunction: () => {
                                    const tool = IModelApp_1.IModelApp.toolAdmin.activeTool;
                                    return tool instanceof PrimitiveTool_1.PrimitiveTool ? tool.iModel.selectionSet.isActive : false;
                                },
                            },
                        ],
                    }, {
                        type: appui_abstract_1.PropertyEditorParamTypes.SuppressEditorLabel,
                        suppressLabelPlaceholder: true,
                    },
                ],
            },
            enum: {
                choices: [
                    { label: SelectionTool.modesMessage("Replace"), value: SelectionMode.Replace },
                    { label: SelectionTool.modesMessage("Add"), value: SelectionMode.Add },
                    { label: SelectionTool.modesMessage("Remove"), value: SelectionMode.Remove },
                ],
            },
        };
    }
    showPrompt(mode, method) {
        let mainMsg = "ElementSet.Prompts.";
        switch (method) {
            case SelectionMethod.Pick:
                mainMsg += "IdentifyElement";
                break;
            case SelectionMethod.Line:
                mainMsg += (0 === this._points.length ? "StartPoint" : "EndPoint");
                break;
            case SelectionMethod.Box:
                mainMsg += (0 === this._points.length ? "StartCorner" : "OppositeCorner");
                break;
        }
        const mainInstruction = ToolAssistance_1.ToolAssistance.createInstruction(this.iconSpec, Tool_1.CoreTools.translate(mainMsg));
        const sections = [];
        switch (method) {
            case SelectionMethod.Pick:
                const mousePickInstructions = [];
                mousePickInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptElement"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                mousePickInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.LeftClickDrag, Tool_1.CoreTools.translate("ElementSet.Inputs.BoxCorners"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                mousePickInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.RightClickDrag, Tool_1.CoreTools.translate("ElementSet.Inputs.CrossingLine"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                mousePickInstructions.push(ToolAssistance_1.ToolAssistance.createModifierKeyInstruction(ToolAssistance_1.ToolAssistance.shiftKey, ToolAssistance_1.ToolAssistanceImage.LeftClickDrag, Tool_1.CoreTools.translate("ElementSet.Inputs.OverlapSelection"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                if (SelectionMode.Replace === mode) {
                    mousePickInstructions.push(ToolAssistance_1.ToolAssistance.createKeyboardInstruction(ToolAssistance_1.ToolAssistance.ctrlKeyboardInfo, Tool_1.CoreTools.translate("ElementSet.Inputs.InvertSelection"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                    mousePickInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.CursorClick, Tool_1.CoreTools.translate("ElementSet.Inputs.ClearSelection"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                }
                sections.push(ToolAssistance_1.ToolAssistance.createSection(mousePickInstructions, ToolAssistance_1.ToolAssistance.inputsLabel));
                const touchPickInstructions = [];
                if (!ToolAssistance_1.ToolAssistance.createTouchCursorInstructions(touchPickInstructions))
                    touchPickInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.OneTouchTap, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptElement"), false, ToolAssistance_1.ToolAssistanceInputMethod.Touch));
                sections.push(ToolAssistance_1.ToolAssistance.createSection(touchPickInstructions, ToolAssistance_1.ToolAssistance.inputsLabel));
                break;
            case SelectionMethod.Line:
                const mouseLineInstructions = [];
                mouseLineInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                if (SelectionMode.Replace === mode)
                    mouseLineInstructions.push(ToolAssistance_1.ToolAssistance.createModifierKeyInstruction(ToolAssistance_1.ToolAssistance.ctrlKey, ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.InvertSelection"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                sections.push(ToolAssistance_1.ToolAssistance.createSection(mouseLineInstructions, ToolAssistance_1.ToolAssistance.inputsLabel));
                const touchLineInstructions = [];
                touchLineInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.OneTouchDrag, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Touch));
                sections.push(ToolAssistance_1.ToolAssistance.createSection(touchLineInstructions, ToolAssistance_1.ToolAssistance.inputsLabel));
                break;
            case SelectionMethod.Box:
                const mouseBoxInstructions = [];
                mouseBoxInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                mouseBoxInstructions.push(ToolAssistance_1.ToolAssistance.createModifierKeyInstruction(ToolAssistance_1.ToolAssistance.shiftKey, ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.OverlapSelection"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                if (SelectionMode.Replace === mode)
                    mouseBoxInstructions.push(ToolAssistance_1.ToolAssistance.createModifierKeyInstruction(ToolAssistance_1.ToolAssistance.ctrlKey, ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.InvertSelection"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse));
                sections.push(ToolAssistance_1.ToolAssistance.createSection(mouseBoxInstructions, ToolAssistance_1.ToolAssistance.inputsLabel));
                const touchBoxInstructions = [];
                touchBoxInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.OneTouchDrag, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Touch));
                sections.push(ToolAssistance_1.ToolAssistance.createSection(touchBoxInstructions, ToolAssistance_1.ToolAssistance.inputsLabel));
                break;
        }
        const instructions = ToolAssistance_1.ToolAssistance.createInstructions(mainInstruction, sections);
        IModelApp_1.IModelApp.notifications.setToolAssistance(instructions);
    }
    initSelectTool() {
        const method = this.selectionMethod;
        const mode = this.selectionMode;
        const enableLocate = SelectionMethod.Pick === method;
        this._isSelectByPoints = false;
        this._points.length = 0;
        this.initLocateElements(enableLocate, false, enableLocate ? "default" : IModelApp_1.IModelApp.viewManager.crossHairCursor, Tool_1.CoordinateLockOverrides.All);
        IModelApp_1.IModelApp.locateManager.options.allowDecorations = true; // Always locate to display tool tip even if we reject for adding to selection set...
        this.showPrompt(mode, method);
    }
    processMiss(_ev) {
        if (!this.iModel.selectionSet.isActive)
            return false;
        this.iModel.selectionSet.emptyAll();
        return true;
    }
    updateSelection(elementId, process) {
        let returnValue = false;
        switch (process) {
            case SelectionProcessing.AddElementToSelection:
                returnValue = this.iModel.selectionSet.add(elementId);
                break;
            case SelectionProcessing.RemoveElementFromSelection:
                returnValue = this.iModel.selectionSet.remove(elementId);
                break;
            case SelectionProcessing.InvertElementInSelection: // (if element is in selection remove it else add it.)
                returnValue = this.iModel.selectionSet.invert(elementId);
                break;
            case SelectionProcessing.ReplaceSelectionWithElement:
                this.iModel.selectionSet.replace(elementId);
                returnValue = true;
                break;
            default:
                return false;
        }
        // always force UI to sync display of options since the select option of Remove should only be enabled if the selection set has elements.
        if (returnValue)
            this.syncSelectionMode();
        return returnValue;
    }
    async processSelection(elementId, process) { return this.updateSelection(elementId, process); }
    useOverlapSelection(ev) {
        if (undefined === ev.viewport)
            return false;
        const pt1 = ev.viewport.worldToView(this._points[0]);
        const pt2 = ev.viewport.worldToView(ev.point);
        const overlapMode = (pt1.x > pt2.x);
        return (ev.isShiftKey ? !overlapMode : overlapMode); // Shift inverts inside/overlap selection...
    }
    selectByPointsDecorate(context) {
        if (!this._isSelectByPoints)
            return;
        const ev = new Tool_1.BeButtonEvent();
        IModelApp_1.IModelApp.toolAdmin.fillEventFromCursorLocation(ev);
        if (undefined === ev.viewport)
            return;
        const vp = context.viewport;
        const bestContrastIsBlack = (core_common_1.ColorDef.black === vp.getContrastToBackgroundColor());
        const crossingLine = (SelectionMethod.Line === this.selectionMethod || (SelectionMethod.Pick === this.selectionMethod && Tool_1.BeButton.Reset === ev.button));
        const overlapSelection = (crossingLine || this.useOverlapSelection(ev));
        const position = vp.worldToView(this._points[0]);
        position.x = Math.floor(position.x) + 0.5;
        position.y = Math.floor(position.y) + 0.5;
        const position2 = vp.worldToView(ev.point);
        position2.x = Math.floor(position2.x) + 0.5;
        position2.y = Math.floor(position2.y) + 0.5;
        const offset = position2.minus(position);
        const drawDecoration = (ctx) => {
            ctx.strokeStyle = bestContrastIsBlack ? "black" : "white";
            ctx.lineWidth = 1;
            if (overlapSelection)
                ctx.setLineDash([5, 5]);
            if (crossingLine) {
                ctx.beginPath();
                ctx.moveTo(0, 0);
                ctx.lineTo(offset.x, offset.y);
                ctx.stroke();
            }
            else {
                ctx.strokeRect(0, 0, offset.x, offset.y);
                ctx.fillStyle = bestContrastIsBlack ? "rgba(0,0,0,.06)" : "rgba(255,255,255,.06)";
                ctx.fillRect(0, 0, offset.x, offset.y);
            }
        };
        context.addCanvasDecoration({ position, drawDecoration });
    }
    selectByPointsProcess(origin, corner, ev, method, overlap) {
        const vp = ev.viewport;
        if (!vp)
            return;
        const pts = [];
        pts[0] = new core_geometry_1.Point2d(Math.floor(origin.x + 0.5), Math.floor(origin.y + 0.5));
        pts[1] = new core_geometry_1.Point2d(Math.floor(corner.x + 0.5), Math.floor(corner.y + 0.5));
        const range = core_geometry_1.Range2d.createArray(pts);
        const rect = new ViewRect_1.ViewRect();
        rect.initFromRange(range);
        const allowTransients = this.wantPickableDecorations();
        vp.readPixels(rect, Pixel_1.Pixel.Selector.Feature, (pixels) => {
            if (undefined === pixels)
                return;
            const sRange = core_geometry_1.Range2d.createNull();
            sRange.extendPoint(core_geometry_1.Point2d.create(vp.cssPixelsToDevicePixels(range.low.x), vp.cssPixelsToDevicePixels(range.low.y)));
            sRange.extendPoint(core_geometry_1.Point2d.create(vp.cssPixelsToDevicePixels(range.high.x), vp.cssPixelsToDevicePixels(range.high.y)));
            pts[0].x = vp.cssPixelsToDevicePixels(pts[0].x);
            pts[0].y = vp.cssPixelsToDevicePixels(pts[0].y);
            pts[1].x = vp.cssPixelsToDevicePixels(pts[1].x);
            pts[1].y = vp.cssPixelsToDevicePixels(pts[1].y);
            let contents = new Set();
            const testPoint = core_geometry_1.Point2d.createZero();
            const getPixelElementId = (pixel) => {
                if (undefined === pixel.elementId || core_bentley_1.Id64.isInvalid(pixel.elementId))
                    return undefined; // no geometry at this location...
                if (!allowTransients && core_bentley_1.Id64.isTransient(pixel.elementId))
                    return undefined; // tool didn't request pickable decorations...
                if (!vp.isPixelSelectable(pixel))
                    return undefined; // reality model, terrain, etc - not selectable
                return pixel.elementId;
            };
            if (SelectionMethod.Box === method) {
                const outline = overlap ? undefined : new Set();
                const offset = sRange.clone();
                offset.expandInPlace(-2);
                for (testPoint.x = sRange.low.x; testPoint.x <= sRange.high.x; ++testPoint.x) {
                    for (testPoint.y = sRange.low.y; testPoint.y <= sRange.high.y; ++testPoint.y) {
                        const pixel = pixels.getPixel(testPoint.x, testPoint.y);
                        const elementId = getPixelElementId(pixel);
                        if (undefined === elementId)
                            continue;
                        if (undefined !== outline && !offset.containsPoint(testPoint))
                            outline.add(elementId.toString());
                        else
                            contents.add(elementId.toString());
                    }
                }
                if (undefined !== outline && 0 !== outline.size) {
                    const inside = new Set();
                    contents.forEach((id) => {
                        if (!outline.has(id))
                            inside.add(id);
                    });
                    contents = inside;
                }
            }
            else {
                const closePoint = core_geometry_1.Point2d.createZero();
                for (testPoint.x = sRange.low.x; testPoint.x <= sRange.high.x; ++testPoint.x) {
                    for (testPoint.y = sRange.low.y; testPoint.y <= sRange.high.y; ++testPoint.y) {
                        const pixel = pixels.getPixel(testPoint.x, testPoint.y);
                        const elementId = getPixelElementId(pixel);
                        if (undefined === elementId)
                            continue;
                        const fraction = testPoint.fractionOfProjectionToLine(pts[0], pts[1], 0.0);
                        pts[0].interpolate(fraction, pts[1], closePoint);
                        if (closePoint.distance(testPoint) < 1.5)
                            contents.add(elementId.toString());
                    }
                }
            }
            if (0 === contents.size) {
                if (!ev.isControlKey && this.wantSelectionClearOnMiss(ev) && this.processMiss(ev))
                    this.syncSelectionMode();
                return;
            }
            switch (this.selectionMode) {
                case SelectionMode.Replace:
                    if (!ev.isControlKey)
                        this.processSelection(contents, SelectionProcessing.ReplaceSelectionWithElement); // eslint-disable-line @typescript-eslint/no-floating-promises
                    else
                        this.processSelection(contents, SelectionProcessing.InvertElementInSelection); // eslint-disable-line @typescript-eslint/no-floating-promises
                    break;
                case SelectionMode.Add:
                    this.processSelection(contents, SelectionProcessing.AddElementToSelection); // eslint-disable-line @typescript-eslint/no-floating-promises
                    break;
                case SelectionMode.Remove:
                    this.processSelection(contents, SelectionProcessing.RemoveElementFromSelection); // eslint-disable-line @typescript-eslint/no-floating-promises
                    break;
            }
        }, true);
    }
    selectByPointsStart(ev) {
        if (Tool_1.BeButton.Data !== ev.button && Tool_1.BeButton.Reset !== ev.button)
            return false;
        this._points.length = 0;
        this._points.push(ev.point.clone());
        this._isSelectByPoints = true;
        IModelApp_1.IModelApp.accuSnap.enableLocate(false);
        IModelApp_1.IModelApp.toolAdmin.setLocateCircleOn(false);
        this.showPrompt(this.selectionMode, this.selectionMethod);
        return true;
    }
    selectByPointsEnd(ev) {
        if (!this._isSelectByPoints)
            return false;
        const vp = ev.viewport;
        if (vp === undefined) {
            this.initSelectTool();
            return false;
        }
        const origin = vp.worldToView(this._points[0]);
        const corner = vp.worldToView(ev.point);
        if (SelectionMethod.Line === this.selectionMethod || (SelectionMethod.Pick === this.selectionMethod && Tool_1.BeButton.Reset === ev.button))
            this.selectByPointsProcess(origin, corner, ev, SelectionMethod.Line, true);
        else
            this.selectByPointsProcess(origin, corner, ev, SelectionMethod.Box, this.useOverlapSelection(ev));
        this.initSelectTool();
        vp.invalidateDecorations();
        return true;
    }
    async onMouseMotion(ev) {
        if (undefined !== ev.viewport && this._isSelectByPoints)
            ev.viewport.invalidateDecorations();
    }
    async selectDecoration(ev, currHit) {
        if (undefined === currHit)
            currHit = await IModelApp_1.IModelApp.locateManager.doLocate(new ElementLocateManager_1.LocateResponse(), true, ev.point, ev.viewport, ev.inputSource);
        if (undefined !== currHit)
            return (currHit.isElementHit ? IModelApp_1.IModelApp.viewManager.overrideElementButtonEvent(currHit, ev) : IModelApp_1.IModelApp.viewManager.onDecorationButtonEvent(currHit, ev));
        return Tool_1.EventHandled.No;
    }
    async processHit(ev, hit) {
        if (hit.isModelHit || hit.isMapHit)
            return Tool_1.EventHandled.No; // model hit = terrain, reality models, background maps, etc - not selectable
        switch (this.selectionMode) {
            case SelectionMode.Replace:
                await this.processSelection(hit.sourceId, ev.isControlKey ? SelectionProcessing.InvertElementInSelection : SelectionProcessing.ReplaceSelectionWithElement);
                break;
            case SelectionMode.Add:
                await this.processSelection(hit.sourceId, SelectionProcessing.AddElementToSelection);
                break;
            case SelectionMode.Remove:
                await this.processSelection(hit.sourceId, SelectionProcessing.RemoveElementFromSelection);
                break;
        }
        return Tool_1.EventHandled.Yes;
    }
    async onMouseStartDrag(ev) {
        IModelApp_1.IModelApp.accuSnap.clear(); // Need to test hit at start drag location, not current AccuSnap...
        if (Tool_1.EventHandled.Yes === await this.selectDecoration(ev))
            return Tool_1.EventHandled.Yes;
        if (Tool_1.InputSource.Touch === ev.inputSource && SelectionMethod.Pick === this.selectionMethod)
            return Tool_1.EventHandled.No; // Require method change for line/box selection...allow IdleTool to handle touch move...
        return this.selectByPointsStart(ev) ? Tool_1.EventHandled.Yes : Tool_1.EventHandled.No;
    }
    async onMouseEndDrag(ev) {
        return this.selectByPointsEnd(ev) ? Tool_1.EventHandled.Yes : Tool_1.EventHandled.No;
    }
    async onDataButtonUp(ev) {
        if (undefined === ev.viewport)
            return Tool_1.EventHandled.No;
        if (this.selectByPointsEnd(ev))
            return Tool_1.EventHandled.Yes;
        if (SelectionMethod.Pick !== this.selectionMethod) {
            if (!ev.isControlKey && this.wantSelectionClearOnMiss(ev) && this.processMiss(ev))
                this.syncSelectionMode();
            if (Tool_1.InputSource.Touch !== ev.inputSource)
                this.selectByPointsStart(ev); // Require touch move and not tap to start crossing line/box selection...
            return Tool_1.EventHandled.Yes;
        }
        const hit = await IModelApp_1.IModelApp.locateManager.doLocate(new ElementLocateManager_1.LocateResponse(), true, ev.point, ev.viewport, ev.inputSource);
        if (hit !== undefined) {
            if (Tool_1.EventHandled.Yes === await this.selectDecoration(ev, hit))
                return Tool_1.EventHandled.Yes;
            if (Tool_1.EventHandled.Yes === await this.processHit(ev, hit))
                return Tool_1.EventHandled.Yes;
        }
        if (!ev.isControlKey && this.wantSelectionClearOnMiss(ev) && this.processMiss(ev))
            this.syncSelectionMode();
        return Tool_1.EventHandled.Yes;
    }
    async onResetButtonUp(ev) {
        if (this._isSelectByPoints) {
            if (undefined !== ev.viewport)
                ev.viewport.invalidateDecorations();
            this.initSelectTool();
            return Tool_1.EventHandled.Yes;
        }
        // Check for overlapping hits...
        const lastHit = SelectionMode.Remove === this.selectionMode ? undefined : IModelApp_1.IModelApp.locateManager.currHit;
        if (lastHit && this.iModel.selectionSet.has(lastHit.sourceId)) {
            const autoHit = IModelApp_1.IModelApp.accuSnap.currHit;
            // Play nice w/auto-locate, only remove previous hit if not currently auto-locating or over previous hit
            if (undefined === autoHit || autoHit.isSameHit(lastHit)) {
                const response = new ElementLocateManager_1.LocateResponse();
                let nextHit;
                do {
                    nextHit = await IModelApp_1.IModelApp.locateManager.doLocate(response, false, ev.point, ev.viewport, ev.inputSource);
                } while (undefined !== nextHit && (nextHit.isModelHit || nextHit.isMapHit)); // Ignore reality models, terrain, maps, etc.
                // remove element(s) previously selected if in replace mode, or if we have a next element in add mode
                if (SelectionMode.Replace === this.selectionMode || undefined !== nextHit)
                    await this.processSelection(lastHit.sourceId, SelectionProcessing.RemoveElementFromSelection);
                // add element(s) located via reset button
                if (undefined !== nextHit)
                    await this.processSelection(nextHit.sourceId, SelectionProcessing.AddElementToSelection);
                return Tool_1.EventHandled.Yes;
            }
        }
        if (Tool_1.EventHandled.Yes === await this.selectDecoration(ev, IModelApp_1.IModelApp.accuSnap.currHit))
            return Tool_1.EventHandled.Yes;
        await IModelApp_1.IModelApp.accuSnap.resetButton();
        return Tool_1.EventHandled.Yes;
    }
    async onSuspend() {
        this._isSuspended = true;
        if (this.wantEditManipulators())
            IModelApp_1.IModelApp.toolAdmin.manipulatorToolEvent.raiseEvent(this, ToolAdmin_1.ManipulatorToolEvent.Suspend);
    }
    async onUnsuspend() {
        this._isSuspended = false;
        if (this.wantEditManipulators())
            IModelApp_1.IModelApp.toolAdmin.manipulatorToolEvent.raiseEvent(this, ToolAdmin_1.ManipulatorToolEvent.Unsuspend);
        this.showPrompt(this.selectionMode, this.selectionMethod);
    }
    async onTouchMoveStart(ev, startEv) {
        if (startEv.isSingleTouch && !this._isSelectByPoints)
            await IModelApp_1.IModelApp.toolAdmin.convertTouchMoveStartToButtonDownAndMotion(startEv, ev);
        return (this._isSuspended || this._isSelectByPoints) ? Tool_1.EventHandled.Yes : Tool_1.EventHandled.No;
    }
    async onTouchMove(ev) {
        if (this._isSelectByPoints)
            return IModelApp_1.IModelApp.toolAdmin.convertTouchMoveToMotion(ev);
    }
    async onTouchComplete(ev) {
        if (this._isSelectByPoints)
            return IModelApp_1.IModelApp.toolAdmin.convertTouchEndToButtonUp(ev);
    }
    async onTouchCancel(ev) {
        if (this._isSelectByPoints)
            return IModelApp_1.IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, Tool_1.BeButton.Reset);
    }
    decorate(context) { this.selectByPointsDecorate(context); }
    async onModifierKeyTransition(_wentDown, modifier, _event) {
        return (modifier === Tool_1.BeModifierKeys.Shift && this._isSelectByPoints) ? Tool_1.EventHandled.Yes : Tool_1.EventHandled.No;
    }
    async filterHit(hit, out) {
        if (!this.wantPickableDecorations() && !hit.isElementHit)
            return ElementLocateManager_1.LocateFilterStatus.Reject;
        const mode = this.selectionMode;
        if (SelectionMode.Replace === mode)
            return ElementLocateManager_1.LocateFilterStatus.Accept;
        const isSelected = this.iModel.selectionSet.has(hit.sourceId);
        const status = ((SelectionMode.Add === mode ? !isSelected : isSelected) ? ElementLocateManager_1.LocateFilterStatus.Accept : ElementLocateManager_1.LocateFilterStatus.Reject);
        if (out && ElementLocateManager_1.LocateFilterStatus.Reject === status)
            out.explanation = Tool_1.CoreTools.translate(`ElementSet.Error.${isSelected ? "AlreadySelected" : "NotSelected"}`);
        return status;
    }
    async onRestartTool() { return this.exitTool(); }
    async onCleanup() {
        if (this.wantEditManipulators())
            IModelApp_1.IModelApp.toolAdmin.manipulatorToolEvent.raiseEvent(this, ToolAdmin_1.ManipulatorToolEvent.Stop);
    }
    async onPostInstall() {
        await super.onPostInstall();
        if (!this.targetView)
            return;
        if (this.wantEditManipulators())
            IModelApp_1.IModelApp.toolAdmin.manipulatorToolEvent.raiseEvent(this, ToolAdmin_1.ManipulatorToolEvent.Start);
        this.initSelectTool();
    }
    static async startTool() { return new SelectionTool().run(); }
    syncSelectionMode() {
        if (SelectionMode.Remove === this.selectionMode && !this.iModel.selectionSet.isActive) {
            // No selection active resetting selection mode since there is nothing to Remove
            this.selectionMode = SelectionMode.Replace;
            this.initSelectTool();
        }
        if (this.wantToolSettings()) {
            const syncMode = { value: this._selectionModeValue, propertyName: SelectionTool._modesName };
            IModelApp_1.IModelApp.toolAdmin.toolSettingsState.saveToolSettingProperty(this.toolId, syncMode);
            this.syncToolSettingsProperties([syncMode]);
        }
    }
    /** Used to supply DefaultToolSettingProvider with a list of properties to use to generate ToolSettings.  If undefined then no ToolSettings will be displayed
     * @beta
     */
    supplyToolSettingsProperties() {
        if (!this.wantToolSettings())
            return undefined;
        // load latest values from session
        IModelApp_1.IModelApp.toolAdmin.toolSettingsState.getInitialToolSettingValues(this.toolId, [SelectionTool._modesName])?.forEach((value) => {
            if (value.propertyName === SelectionTool._modesName)
                this._selectionModeValue = value.value;
        });
        // Make sure a mode of SelectionMode.Remove is valid
        if (SelectionMode.Remove === this.selectionMode && !this.iModel.selectionSet.isActive) {
            this.selectionMode = SelectionMode.Replace;
            IModelApp_1.IModelApp.toolAdmin.toolSettingsState.saveToolSettingProperty(this.toolId, { propertyName: SelectionTool._modesName, value: this._selectionModeValue });
        }
        const toolSettings = new Array();
        // generate 3 columns - label will be placed in column 0 and button group editors in columns 1 and 2.
        toolSettings.push({ value: this._selectionMethodValue, property: SelectionTool._getMethodsDescription(), editorPosition: { rowPriority: 0, columnIndex: 1 } });
        toolSettings.push({ value: this._selectionModeValue, property: SelectionTool._getModesDescription(), editorPosition: { rowPriority: 0, columnIndex: 2 } });
        return toolSettings;
    }
    /** Used to send changes from UI back to Tool
     * @beta
     */
    async applyToolSettingPropertyChange(updatedValue) {
        let changed = false;
        if (updatedValue.propertyName === SelectionTool._methodsName) {
            const saveWantManipulators = this.wantEditManipulators();
            this._selectionMethodValue = updatedValue.value;
            if (this._selectionMethodValue) {
                const currWantManipulators = this.wantEditManipulators();
                if (saveWantManipulators !== currWantManipulators)
                    IModelApp_1.IModelApp.toolAdmin.manipulatorToolEvent.raiseEvent(this, currWantManipulators ? ToolAdmin_1.ManipulatorToolEvent.Start : ToolAdmin_1.ManipulatorToolEvent.Stop);
                changed = true;
            }
        }
        if (updatedValue.propertyName === SelectionTool._modesName) {
            this._selectionModeValue = updatedValue.value;
            if (this._selectionModeValue) {
                if (this.wantToolSettings())
                    IModelApp_1.IModelApp.toolAdmin.toolSettingsState.saveToolSettingProperty(this.toolId, { propertyName: SelectionTool._modesName, value: this._selectionModeValue });
                changed = true;
            }
        }
        if (changed)
            this.initSelectTool();
        return true; // return true if change is valid
    }
}
exports.SelectionTool = SelectionTool;
SelectionTool.hidden = false;
SelectionTool.toolId = "Select";
SelectionTool.iconSpec = "icon-cursor";
SelectionTool._methodsName = "selectionMethods";
SelectionTool._modesName = "selectionModes";
//# sourceMappingURL=SelectTool.js.map