"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 Tools
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrimitiveTool = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const IModelApp_1 = require("../IModelApp");
const NotificationManager_1 = require("../NotificationManager");
const AccuDrawTool_1 = require("./AccuDrawTool");
const Tool_1 = require("./Tool");
/** The PrimitiveTool class can be used to implement tools to create or modify geometric elements.
 * @see [Writing a PrimitiveTool]($docs/learning/frontend/primitivetools.md)
 * @public
 * @extensions
 */
class PrimitiveTool extends Tool_1.InteractiveTool {
    constructor() {
        super(...arguments);
        this.targetIsLocked = false; // If target model is known, set this to true in constructor and override getTargetModel.
    }
    get targetModelId() { return this._targetModelId; }
    set targetModelId(v) { this._targetModelId = v; }
    /** Get the iModel on which this tool operates.
     * @note The iModel is obtained from [[targetView]], so should only be invoked if the tool installed successfully.
     */
    get iModel() {
        (0, core_bentley_1.assert)(undefined !== this.targetView);
        return this.targetView.view.iModel;
    }
    /** Get the briefcase on which this tool operates, if the tool has successfully installed and the target [[iModel]] is a briefcase. */
    get briefcase() {
        const iModel = this.targetView?.view.iModel;
        return iModel?.isBriefcaseConnection() ? iModel : undefined;
    }
    /**
     * Establish this tool as the active PrimitiveTool.
     * @return true if this tool was installed (though it may have exited too)
     * @note If you override this method you **must** call `super.run` and return false if it returns false.
     */
    async run(..._args) {
        const { toolAdmin, viewManager } = IModelApp_1.IModelApp;
        if (!this.isCompatibleViewport(viewManager.selectedView, false) || !await toolAdmin.onInstallTool(this))
            return false;
        await toolAdmin.startPrimitiveTool(this);
        await toolAdmin.onPostInstallTool(this);
        return true;
    }
    /** Determine whether the supplied Viewport is compatible with this tool.
     * @param vp the Viewport to check
     */
    isCompatibleViewport(vp, isSelectedViewChange) {
        if (undefined === vp)
            return false; // No views are open...
        const view = vp.view;
        const iModel = view.iModel;
        if (this.requireWriteableTarget() && iModel.isReadonly)
            return false; // this Tool can't be used when iModel is read only.
        if (undefined === this.targetView || (!this.targetIsLocked && isSelectedViewChange))
            this.targetView = vp; // Update target to new view if undefined or still free to change.
        if (undefined === this.targetModelId && (!this.targetIsLocked || vp === this.targetView))
            return true; // Accept if this view is current target or any type of model/view is still ok as target is still free to change.
        if (iModel !== this.iModel)
            return false; // Once a ViewState has been established, only accept viewport showing the same iModel.
        if (this.targetModelId)
            return view.viewsModel(this.targetModelId); // If a specific target model is specified, only allow view that shows it.
        if (view.isSpatialView() && this.targetView.view.isSpatialView())
            return true; // No specific target, two spatial views are considered compatible.
        let allowView = false;
        view.forEachModel((model) => {
            if (!allowView && this.targetView.view.viewsModel(model.id))
                allowView = true;
        });
        return allowView; // Accept if this view shares a model in common with target.
    }
    /**
     * Checks that the adjusted point from the supplied button event is within the project extents for spatial views. The range of physical geometry
     * should always be fully inside the project extents. Only checking the adjusted point won't absolutely guarantee that a tool doesn't create/move geometry
     * outside the project extents, but it will be sufficient to handle most cases and provide good feedback to the user.
     * @return true if ev is acceptable.
     */
    isValidLocation(ev, isButtonEvent) {
        const vp = ev.viewport;
        if (undefined === vp)
            return false;
        if (isButtonEvent && Tool_1.BeButton.Data !== ev.button)
            return true;
        const view = vp.view;
        if (!view.isSpatialView())
            return true;
        // NOTE: If points aren't being adjusted then the tool shouldn't be creating geometry currently (ex. locating elements) and we shouldn't filter point...
        if (0 !== (IModelApp_1.IModelApp.toolAdmin.toolState.coordLockOvr & Tool_1.CoordinateLockOverrides.ACS))
            return true;
        // We know the tool isn't doing a locate, we don't know what it will do with this point. Minimize erroneous filtering by restricting the check to when AccuSnap is tool enable (not user enabled)...
        if (!IModelApp_1.IModelApp.accuSnap.isSnapEnabled)
            return true;
        const extents = view.iModel.projectExtents;
        if (extents.containsPoint(ev.point))
            return true;
        if (isButtonEvent && ev.isDown)
            IModelApp_1.IModelApp.notifications.outputMessage(new NotificationManager_1.NotifyMessageDetails(NotificationManager_1.OutputMessagePriority.Error, Tool_1.CoreTools.translate("ElementSet.Error.ProjectExtents")));
        return false;
    }
    /** Called on data button down event to lock the tool to its current target model. */
    autoLockTarget() {
        if (undefined === this.targetView)
            return;
        this.targetIsLocked = true;
    }
    /**  Returns the prompt based on the tool's current state. */
    getPrompt() { return ""; }
    /** Called from isCompatibleViewport to check for a read only iModel, which is not a valid target for tools that create or modify elements. */
    requireWriteableTarget() { return true; }
    /**
     * Called when active view changes. Tool may choose to restart or exit based on current view type.
     * @param _previous The previously active view.
     * @param current The new active view.
     */
    async onSelectedViewportChanged(_previous, current) {
        if (this.isCompatibleViewport(current, true))
            return;
        return this.onRestartTool();
    }
    /**
     * Called to reset tool to initial state. PrimitiveTool implements this method to call onRestartTool.
     */
    async onReinitialize() { return this.onRestartTool(); }
    async exitTool() { return IModelApp_1.IModelApp.toolAdmin.startDefaultTool(); }
    /**
     * Called to reverse to a previous tool state (ex. undo last data button).
     * @return false to instead reverse the most recent transaction.
     */
    async onUndoPreviousStep() { return false; }
    /** @internal */
    async undoPreviousStep() {
        if (!await this.onUndoPreviousStep())
            return false;
        AccuDrawTool_1.AccuDrawShortcuts.processPendingHints(); // Process pending hints from onUndoPreviousStep before calling updateDynamics...
        IModelApp_1.IModelApp.viewManager.invalidateDecorationsAllViews();
        IModelApp_1.IModelApp.toolAdmin.updateDynamics(undefined, undefined, true); // Don't wait for motion to update dynamics...
        return true;
    }
    /**
     * Called to reinstate to a previous tool state (ex. redo last data button).
     * @return false to instead reinstate the most recent transaction.
     */
    async onRedoPreviousStep() { return false; }
    /** @internal */
    async redoPreviousStep() {
        if (!await this.onRedoPreviousStep())
            return false;
        AccuDrawTool_1.AccuDrawShortcuts.processPendingHints(); // Process pending hints from onRedoPreviousStep before calling updateDynamics...
        IModelApp_1.IModelApp.viewManager.invalidateDecorationsAllViews();
        IModelApp_1.IModelApp.toolAdmin.updateDynamics(undefined, undefined, true); // Don't wait for motion to update dynamics...
        return true;
    }
    /** If this tool is editing a briefcase, commits any elements that the tool has changed, supplying the tool name as the undo string. */
    async saveChanges() {
        if (this.iModel.isBriefcaseConnection())
            return this.iModel.saveChanges(this.toolId);
    }
}
exports.PrimitiveTool = PrimitiveTool;
//# sourceMappingURL=PrimitiveTool.js.map