/*!
 * Copyright (c) 2026 Vertex Software LLC. All rights reserved.
 */
'use strict';

var index = require('./index-C_J7n4Dv.js');
var bundle$1 = require('./bundle-BhTmftd-.js');
var cursors = require('./cursors-BjfBRMe4.js');
var dom = require('./dom-CXWXs9Hx.js');
require('./geometry_pb-C1IfJb14.js');
require('./wrappers_pb-Dupo3Hcb.js');
require('./wrappers_pb-DCb8h8QZ.js');
require('./bundle-Dsc8KJRQ.js');
var streamAttributes = require('./streamAttributes-ce88H9XR.js');
var model = require('./model-CNYtD2Ho.js');
var overlays = require('./overlays-CxiJArpE.js');
var results = require('./results-BDTeOJrm.js');
require('./entities-BhIrCJqA.js');
var viewport = require('./viewport-ZSVJIQTY.js');
var bundle = require('./bundle-96sYCpzz.js');
var utils = require('./utils-B7r28RVu.js');
var index$1 = require('./index-CRUoPh1y.js');
require('./_commonjsHelpers-CBbBosIU.js');

/**
 * The default distance, in pixels, between the mouse position and a snappable
 * measurement feature.
 */
const MEASUREMENT_SNAP_DISTANCE = 16;
/**
 * The default length, in pixels, for the measurement line cap.
 */
const MEASUREMENT_LINE_CAP_LENGTH = 10;

function getMeasurementBoundingClientRect(el) {
    return el.getBoundingClientRect();
}

class PointToPointHitTester {
    constructor(stencil, depthBuffer, viewport, camera) {
        this.stencil = stencil;
        this.depthBuffer = depthBuffer;
        this.viewport = viewport;
        this.camera = camera;
    }
    hitTest(pt) {
        const dpt = this.viewport.transformPointToFrame(pt, this.depthBuffer);
        if (this.stencil != null) {
            const spt = this.viewport.transformPointToFrame(pt, this.stencil);
            return this.stencil.hitTest(spt) || this.depthBuffer.hitTest(dpt);
        }
        else {
            return this.depthBuffer.hitTest(dpt);
        }
    }
    snapToNearestPixel(pt, radius) {
        if (this.stencil != null) {
            const framePt = this.viewport.transformPointToFrame(pt, this.stencil);
            const snapPt = this.stencil.snapToNearestPixel(framePt, radius);
            return this.viewport.transformPointToViewport(snapPt, this.stencil);
        }
        else {
            return pt;
        }
    }
    transformPointToWorld(pt, { ignoreHitTest = false } = {}) {
        const buffer = this.pickDepthBuffer(pt);
        if (buffer != null) {
            return this.viewport.transformPointToWorldSpace(pt, buffer);
        }
        else if (ignoreHitTest) {
            return this.viewport.transformPointToWorldSpace(pt, this.depthBuffer);
        }
    }
    pickDepthBuffer(pt) {
        if (this.stencil != null) {
            const stencilPt = this.viewport.transformPointToFrame(pt, this.stencil);
            if (this.stencil.hitTest(stencilPt)) {
                return this.stencil.depthBuffer;
            }
        }
        const depthPt = this.viewport.transformPointToFrame(pt, this.depthBuffer);
        return this.depthBuffer.hitTest(depthPt) ? this.depthBuffer : undefined;
    }
}

/**
 * A controller to handle point-to-point measurement interactions.
 */
class PointToPointInteractionController {
    constructor(model) {
        this.model = model;
    }
    /**
     * Returns an interaction handler to perform a new measurement.
     *
     * @param pt A point in viewport coordinates.
     * @param hits A provider to perform hits.
     * @returns An interaction handler, or `undefined` if the point does not hit
     *  any geometry.
     */
    newMeasurement(pt, hits) {
        const world = hits.hitTester().transformPointToWorld(pt);
        return world != null
            ? new NewMeasurementInteraction(pt, world, this.model)
            : undefined;
    }
    /**
     * Returns an interaction handler to edit an existing measurement.
     *
     * @param anchor The anchor to edit.
     * @returns An interaction handler to edit a measurement.
     */
    editMeasurement(anchor) {
        const measurement = this.model.getMeasurement();
        if (measurement == null) {
            throw new Error('Cannot edit measurement. Measurement is empty.');
        }
        return anchor === 'start'
            ? new EditStartAnchorInteraction(measurement, this.model)
            : new EditEndAnchorInteraction(measurement, this.model);
    }
    /**
     * Clears the position of the feedback indicator.
     */
    clearIndicator() {
        this.model.setIndicator(undefined);
    }
    /**
     * Updates the position of the feedback indicator.
     *
     * @param pt A point, in viewport coordinates.
     * @param hits A provider to perform hits.
     * @returns `true` if the indicator is over geometry.
     */
    moveIndicator(pt, hits) {
        const worldPt = hits.hitTester().transformPointToWorld(pt);
        this.model.setIndicator(worldPt);
        return worldPt != null;
    }
}
/**
 * The model for point-to-point interactions.
 */
class PointToPointInteractionModel {
    constructor() {
        this.measurementChanged = new bundle.EventDispatcher();
        this.indicatorChanged = new bundle.EventDispatcher();
    }
    /**
     * Returns a model that doesn't have the indicator or measurement set.
     */
    static empty() {
        return new PointToPointInteractionModel();
    }
    /**
     * Returns the current measurement for the interaction.
     */
    getMeasurement() {
        return this.measurement;
    }
    /**
     * Updates the measurement and emits a change event.
     *
     * @param measurement The new measurement.
     */
    setMeasurement(measurement) {
        if (this.measurement !== measurement) {
            this.measurement = measurement;
            this.measurementChanged.emit(measurement);
        }
    }
    /**
     * Updates the measurement from the given values. If `start` or `end` are
     * `undefined` the measurement will be cleared.
     *
     * @param start The start point.
     * @param end The end point.
     * @param valid `true` if the measurement is valid.
     */
    setMeasurementFromValues(start, end, valid) {
        if (start != null && end != null) {
            const measurement = valid
                ? validMeasurement(start, end)
                : invalidMeasurement(start, end);
            this.setMeasurement(measurement);
        }
        else {
            this.setMeasurement(undefined);
        }
    }
    /**
     * Adds a callback that is invoked when the measurement changes.
     *
     * @param listener The callback to register.
     * @returns A `Disposable` to remove the event listener.
     */
    onMeasurementChanged(listener) {
        return this.measurementChanged.on(listener);
    }
    /**
     * Gets the position representing the feedback indicator.
     */
    getIndicator() {
        return this.indicator;
    }
    /**
     * Updates the position of the feedback indicator.
     */
    setIndicator(pt) {
        if (this.indicator !== pt) {
            this.indicator = pt;
            this.indicatorChanged.emit(pt);
        }
    }
    /**
     * Adds a callback that is invoked when the feedback indicator position
     * changes.
     *
     * @param listener The callback to register.
     * @returns A `Disposable` to remove the event listener.
     */
    onIndicatorChanged(listener) {
        return this.indicatorChanged.on(listener);
    }
}
/**
 * An interaction handler to perform a new measurement. As a user is performing
 * an interaction, the handler will fetch the position of the starting anchor.
 * While fetching the position, a position from the depth buffer will be used.
 */
class NewMeasurementInteraction {
    constructor(pt, world, model) {
        this.pt = pt;
        this.world = world;
        this.model = model;
        this.hitWorldValid = true;
        // Prevents any updates of the interaction while finish is completing.
        this.preventUpdate = false;
    }
    fetchStartIfMissing(hits) {
        if (this.pendingStart == null) {
            this.pendingStart = this.fetchStart(hits);
        }
    }
    async fetchStart(hits) {
        const raycaster = await hits.raycaster();
        const hitPt = await getHit(raycaster, this.pt);
        if (hitPt == null) {
            // If the hit is empty, fallback to using a point derived from the depth
            // buffer.
            const invalidPt = hits.hitTester().transformPointToWorld(this.pt);
            this.hitWorld = invalidPt;
            this.hitWorldValid = false;
        }
        else {
            this.hitWorld = hitPt;
            this.hitWorldValid = true;
        }
    }
    update(pt, hits) {
        var _a;
        if (!this.preventUpdate) {
            this.fetchStartIfMissing(hits);
            const end = hits
                .hitTester()
                .transformPointToWorld(pt, { ignoreHitTest: true });
            const isHit = hits.hitTester().hitTest(pt);
            const start = (_a = this.hitWorld) !== null && _a !== void 0 ? _a : this.world;
            if (end == null) {
                throw new Error('Cannot update new measurement interaction. End point is empty.');
            }
            else if (!this.hitWorldValid || !isHit) {
                this.model.setIndicator(end);
                this.setMeasurement(invalidMeasurement(start, end));
            }
            else {
                this.model.setIndicator(end);
                this.setMeasurement(validMeasurement(start, end));
            }
        }
    }
    async finish(pt, hits) {
        var _a;
        this.preventUpdate = true;
        // Wait for the hit for the starting point.
        this.fetchStartIfMissing(hits);
        await this.pendingStart;
        this.model.setIndicator(undefined);
        const raycaster = await hits.raycaster();
        const hitPt = await getHit(raycaster, pt);
        const start = (_a = this.hitWorld) !== null && _a !== void 0 ? _a : this.world;
        this.preventUpdate = false;
        if (hitPt == null) {
            const end = hits
                .hitTester()
                .transformPointToWorld(pt, { ignoreHitTest: true });
            if (end == null) {
                throw new Error('Cannot complete new measurement interaction. End point is empty.');
            }
            return this.setMeasurement(invalidMeasurement(start, end));
        }
        else if (!this.hitWorldValid) {
            return this.setMeasurement(invalidMeasurement(start, hitPt));
        }
        else {
            return this.setMeasurement(validMeasurement(start, hitPt));
        }
    }
    setMeasurement(measurement) {
        this.model.setMeasurement(measurement);
        return measurement;
    }
}
/**
 * Base class for an edit anchor interaction.
 *
 * @see {@link EditStartAnchorInteraction}
 * @see {@link EditEndAnchorInteraction}
 */
class EditAnchorInteraction {
    constructor(measurement, model) {
        this.measurement = measurement;
        this.model = model;
    }
    update(pt, context) {
        const world = context
            .hitTester()
            .transformPointToWorld(pt, { ignoreHitTest: true });
        const isHit = context.hitTester().hitTest(pt);
        if (world == null) {
            throw new Error('Cannot update new measurement interaction. End point is empty.');
        }
        else if (!isHit) {
            this.model.setIndicator(world);
            this.setMeasurement(this.getInvalidMeasurement(world));
        }
        else {
            this.model.setIndicator(world);
            this.setMeasurement(this.getValidMeasurement(world));
        }
    }
    async finish(pt, hits) {
        const raycaster = await hits.raycaster();
        const hitPt = await getHit(raycaster, pt);
        this.model.setIndicator(undefined);
        if (hitPt == null) {
            const end = hits
                .hitTester()
                .transformPointToWorld(pt, { ignoreHitTest: true });
            if (end == null) {
                throw new Error('Cannot complete edit measurement interaction. End point is empty.');
            }
            return this.setMeasurement(this.getInvalidMeasurement(end));
        }
        else {
            return this.setMeasurement(this.getValidMeasurement(hitPt));
        }
    }
    setMeasurement(measurement) {
        this.model.setMeasurement(measurement);
        return measurement;
    }
}
class EditStartAnchorInteraction extends EditAnchorInteraction {
    constructor(measurement, model) {
        super(measurement, model);
    }
    getInvalidMeasurement(startPt) {
        return invalidMeasurement(startPt, this.measurement.end);
    }
    getValidMeasurement(startPt) {
        return validMeasurement(startPt, this.measurement.end);
    }
}
class EditEndAnchorInteraction extends EditAnchorInteraction {
    constructor(measurement, model) {
        super(measurement, model);
    }
    getInvalidMeasurement(endPt) {
        return invalidMeasurement(this.measurement.start, endPt);
    }
    getValidMeasurement(endPt) {
        return validMeasurement(this.measurement.start, endPt);
    }
}
async function getHit(raycaster, pt) {
    var _a;
    const hitRes = await raycaster.hitItems(pt);
    const [hit] = (_a = hitRes === null || hitRes === void 0 ? void 0 : hitRes.hits) !== null && _a !== void 0 ? _a : [];
    if ((hit === null || hit === void 0 ? void 0 : hit.hitPoint) != null) {
        const hitPt = streamAttributes.fromPbVector3f(hit.hitPoint);
        if (bundle.mapper.isInvalid(hitPt)) {
            throw new Error(`Invalid hit response [${hitPt.errors.join(',')}]`);
        }
        return hitPt;
    }
    else
        return undefined;
}
function validMeasurement(start, end) {
    return { start, end, distance: bundle$1.vector3.distance(start, end), valid: true };
}
function invalidMeasurement(start, end) {
    return { start, end, valid: false };
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const DistanceMeasurementRenderer = ({ startPt, endPt, centerPt, indicatorPt, distance, anchorLabelOffset, lineCapLength, linePointerEvents, hideStartAnchor, hideEndAnchor, onStartAnchorPointerDown, onEndAnchorPointerDown, }) => {
    const angle = startPt != null && endPt != null
        ? bundle$1.angle.fromPoints(startPt, endPt)
        : undefined;
    const startLabelPt = angle != null && startPt != null && anchorLabelOffset != null
        ? bundle$1.point.add(startPt, bundle$1.point.polar(-anchorLabelOffset, angle))
        : undefined;
    const endLabelPt = angle != null && endPt != null && anchorLabelOffset != null
        ? bundle$1.point.add(endPt, bundle$1.point.polar(anchorLabelOffset, angle))
        : undefined;
    return (index.h("div", null, startPt != null && endPt != null && (index.h("vertex-viewer-measurement-line", { class: index$1.classname('line', {
            'hide-start-line-cap': hideStartAnchor,
            'hide-end-line-cap': hideEndAnchor,
        }), start: startPt, end: endPt, capLength: lineCapLength, pointerEvents: linePointerEvents })), !hideStartAnchor && startPt != null && (index.h("div", { id: "start-anchor", class: "anchor anchor-start", style: { transform: dom.cssTransformCenterAt(startPt) }, onPointerDown: onStartAnchorPointerDown }, index.h("slot", { name: "start-anchor" }, index.h("div", { class: "anchor-placeholder" })))), !hideStartAnchor && startLabelPt && (index.h("div", { class: "anchor-label anchor-label-start", style: { transform: dom.cssTransformCenterAt(startLabelPt) } }, index.h("slot", { name: "start-label" }))), !hideEndAnchor && endPt != null && (index.h("div", { id: "end-anchor", class: "anchor anchor-end", style: { transform: dom.cssTransformCenterAt(endPt) }, onPointerDown: onEndAnchorPointerDown }, index.h("slot", { name: "end-anchor" }, index.h("div", { class: "anchor-placeholder" })))), !hideEndAnchor && endLabelPt && (index.h("div", { class: "anchor-label anchor-label-end", style: { transform: dom.cssTransformCenterAt(endLabelPt) } }, index.h("slot", { name: "end-label" }))), centerPt != null && (index.h("div", { id: "label", class: "distance-label", style: { transform: dom.cssTransformCenterAt(centerPt) } }, distance)), indicatorPt != null && (index.h("div", { class: "indicator", style: { transform: dom.cssTransformCenterAt(indicatorPt) } }, index.h("slot", { name: "indicator" }, index.h("div", { class: "indicator-placeholder" }))))));
};

const viewerMeasurementDistanceCss = () => `:host{--viewer-measurement-distance-accent-color:var(--neutral-900);--viewer-measurement-distance-contrast-color:var(--white);--viewer-measurement-distance-invalid-accent-color:var(--red-600);--viewer-measurement-distance-invalid-contrast-color:var(--white);--viewer-measurement-distance-line-stroke-width:1.25px;--viewer-measurement-distance-line-fill-width:2px;--viewer-measurement-distance-distance-border:1px solid     var(--viewer-measurement-distance-accent-color);--viewer-measurement-distance-distance-border-radius:0.25rem;--viewer-measurement-distance-distance-padding:0.375rem 0.5rem;color:var(--viewer-measurement-distance-accent-color);font-size:0.75rem;position:absolute;top:0;bottom:0;left:0;right:0;overflow:hidden;pointer-events:none}:host([invalid]:not([invalid='false'])){--viewer-measurement-distance-accent-color:var(     --viewer-measurement-distance-invalid-accent-color   ) !important;--viewer-measurement-distance-contrast-color:var(     --viewer-measurement-distance-invalid-contrast-color   ) !important}.line{--viewer-measurement-line-stroke:var(     --viewer-measurement-distance-contrast-color   );--viewer-measurement-line-stroke-opacity:0.75;--viewer-measurement-line-stroke-width:var(     --viewer-measurement-distance-line-stroke-width   );--viewer-measurement-line-fill:var(     --viewer-measurement-distance-accent-color   );--viewer-measurement-line-fill-width:var(     --viewer-measurement-distance-line-fill-width   );position:absolute;width:100%;height:100%}.anchor{position:absolute;border-radius:50%;transform:translate(-50%, -50%);display:block;pointer-events:auto}:host([interacting-anchor='start']) .anchor-start,:host([interacting-anchor='start']) .anchor-label-start{visibility:hidden}.hide-start-line-cap,:host([interacting-anchor='start']) .line{--viewer-measurement-line-start-cap-visibility:hidden}:host([interacting-anchor='end']) .anchor-end,:host([interacting-anchor='end']) .anchor-label-end{visibility:hidden}.hide-end-line-cap,:host([interacting-anchor='end']) .line{--viewer-measurement-line-end-cap-visibility:hidden}.anchor-label{position:absolute;border-radius:50%;transform:translate(-50%, -50%);display:block;pointer-events:auto}:host(:not([mode=''])){cursor:inherit}:host(:not([mode=''])) .anchor{cursor:move}:host([mode='']) .anchor{visibility:hidden}.anchor-placeholder{background:var(--viewer-measurement-distance-contrast-color);width:12px;height:12px;border-radius:50%;box-sizing:border-box}:host(:not([mode=''])) .anchor-placeholder{border:2px solid var(--viewer-measurement-distance-accent-color);background:var(--viewer-measurement-distance-contrast-color)}:host([mode='']) .distance-label{color:var(--viewer-measurement-distance-contrast-color);background:var(--viewer-measurement-distance-accent-color)}.distance-label{position:absolute;background:var(--viewer-measurement-distance-contrast-color);border:var(--viewer-measurement-distance-distance-border);border-radius:var(--viewer-measurement-distance-distance-border-radius);padding:var(--viewer-measurement-distance-distance-padding);pointer-events:auto}.indicator{position:absolute}.indicator-placeholder{background:var(--viewer-measurement-distance-accent-color);border:1px solid var(--viewer-measurement-distance-contrast-color);width:6px;height:6px;border-radius:50%;box-sizing:border-box}`;

const INTERACTION_THRESHOLD = 3;
const ViewerMeasurementDistance = class {
    constructor(hostRef) {
        index.registerInstance(this, hostRef);
        this.editBegin = index.createEvent(this, "editBegin");
        this.editEnd = index.createEvent(this, "editEnd");
        /**
         * Enables the display of axis reference lines between the start and end
         * point.
         */
        this.showAxisReferenceLines = false;
        /**
         * The distance, in pixels, between the mouse and nearest snappable edge. A
         * value of 0 disables snapping.
         */
        this.snapDistance = MEASUREMENT_SNAP_DISTANCE;
        /**
         * The unit of measurement.
         */
        this.units = 'millimeters';
        /**
         * The number of fraction digits to display.
         */
        this.fractionalDigits = 2;
        /**
         * The distance from an anchor to its label.
         */
        this.anchorLabelOffset = 20;
        /**
         * The length of the caps at each end of the distance measurement.
         */
        this.lineCapLength = MEASUREMENT_LINE_CAP_LENGTH;
        /**
         * A mode that specifies how the measurement component should behave.
         *
         * When unset, the component will not respond to interactions with the
         * handles.
         *
         * When `edit`, the measurement anchors are interactive and the user is able
         * to reposition them.
         *
         * When `replace`, anytime the user clicks on the canvas, a new measurement
         * will be created and replace any existing measurement. After a measurement
         * is created, the measurement will be editable.
         */
        this.mode = '';
        /**
         * A property that reflects which anchor is currently being interacted with.
         */
        this.interactingAnchor = 'none';
        /**
         * Indicates if the measurement is invalid. A measurement is invalid if either
         * the start or end position are not on the surface of the model.
         */
        this.invalid = false;
        /**
         * The measurement model that will be updated when this measurement changes.
         * You can pass this to a <vertex-viewer-measurement-details> component to
         * display measurement outcomes.
         */
        this.measurementModel = new model.MeasurementModel();
        this.viewport = new viewport.Viewport(0, 0);
        this.interactionCount = 0;
        this.invalidateStateCounter = 0;
        this.stateMap = {};
        this.measurementUnits = new streamAttributes.DistanceUnits(this.units);
        this.model = PointToPointInteractionModel.empty();
        this.controller = new PointToPointInteractionController(this.model);
        this.overlays = new overlays.MeasurementOverlayManager();
        this.isUserInteractingWithModel = false;
        this.handleFrameDrawn = () => {
            this.invalidateState();
        };
        this.clearIndicator = () => {
            this.controller.clearIndicator();
        };
        this.updateStartAnchor = async (event) => {
            await this.getStencilBuffer();
            if (this.interactionCount === 0) {
                const pt = dom.getMouseClientPosition(event, this.elementBounds);
                const snapPt = this.snapPoint(pt, event);
                await this.updateIndicator(snapPt);
            }
        };
        this.newMeasurement = (event) => {
            if (this.interactionCount === 0 && event.button === 0) {
                // Function that registers event listeners to perform a new measurement.
                const startMeasurement = (start) => {
                    const dispose = () => {
                        window.removeEventListener('pointerup', pointerUp);
                        window.removeEventListener('pointermove', pointerMove);
                    };
                    const pointerUp = async () => {
                        dispose();
                        start();
                    };
                    const pointerMove = (event) => {
                        if (event.buttons > 0) {
                            dispose();
                        }
                    };
                    window.addEventListener('pointermove', pointerMove);
                    window.addEventListener('pointerup', pointerUp);
                };
                // Function that registers event listeners to detect if a user is
                // interacting with the model. If so, we temporarily disable measurement
                // updates until the user finishes.
                const pointerDownAndMove = (callback) => {
                    let downPt = bundle$1.point.create(0, 0);
                    const pointerMove = (event) => {
                        const dist = bundle$1.point.distance(downPt, dom.getMouseClientPosition(event, this.elementBounds));
                        if (dist >= INTERACTION_THRESHOLD) {
                            callback();
                        }
                    };
                    const dispose = () => {
                        window.removeEventListener('pointermove', pointerMove);
                        window.removeEventListener('pointerup', pointerUp);
                    };
                    const pointerUp = () => dispose();
                    const pointerDown = (event) => {
                        downPt = dom.getMouseClientPosition(event, this.elementBounds);
                        window.addEventListener('pointermove', pointerMove);
                        window.addEventListener('pointerup', pointerUp);
                    };
                    window.addEventListener('pointerdown', pointerDown);
                    return {
                        dispose: () => window.removeEventListener('pointerdown', pointerDown),
                    };
                };
                // Function that registers event listeners to finish a measurement.
                const measureInteraction = () => {
                    const handleDownAndMove = pointerDownAndMove(() => {
                        var _a;
                        this.isUserInteractingWithModel = true;
                        (_a = this.stateMap.hoverCursor) === null || _a === void 0 ? void 0 : _a.dispose();
                    });
                    const dispose = () => {
                        window.removeEventListener('pointermove', pointerMove);
                        window.removeEventListener('pointerup', pointerUp);
                        handleDownAndMove.dispose();
                    };
                    const pointerMove = this.createInteractionMoveHandler();
                    const pointerUp = async (event) => {
                        var _a;
                        if (event.button === 0) {
                            if (this.isUserInteractingWithModel) {
                                this.isUserInteractingWithModel = false;
                            }
                            else {
                                const hits = this.getHitProvider();
                                if (hits != null) {
                                    const pt = dom.getMouseClientPosition(event, this.elementBounds);
                                    const snapPt = this.snapPoint(pt, event);
                                    await ((_a = this.interaction) === null || _a === void 0 ? void 0 : _a.finish(snapPt, hits));
                                    dispose();
                                    this.updateMeasurementPropsFromModel();
                                    this.endEditing();
                                }
                            }
                        }
                    };
                    this.beginEditing('replace', 'end');
                    this.newInteractionHandler = { dispose };
                    window.addEventListener('pointermove', pointerMove);
                    window.addEventListener('pointerup', pointerUp);
                };
                const hits = this.getHitProvider();
                if (hits != null) {
                    const pt = dom.getMouseClientPosition(event, this.elementBounds);
                    const snapPt = this.snapPoint(pt, event);
                    this.interaction = this.controller.newMeasurement(snapPt, hits);
                    if (this.interaction != null) {
                        startMeasurement(measureInteraction);
                    }
                }
            }
        };
    }
    /**
     * Computes the bounding boxes of the anchors and label. **Note:** invoking
     * this function uses `getBoundingClientRect` internally and will cause a
     * relayout of the DOM.
     */
    async computeElementMetrics() {
        var _a, _b, _c;
        const startAnchorEl = (_a = this.hostEl.shadowRoot) === null || _a === void 0 ? void 0 : _a.getElementById('start-anchor');
        const endAnchorEl = (_b = this.hostEl.shadowRoot) === null || _b === void 0 ? void 0 : _b.getElementById('end-anchor');
        const labelEl = (_c = this.hostEl.shadowRoot) === null || _c === void 0 ? void 0 : _c.getElementById('label');
        if (startAnchorEl != null && endAnchorEl != null && labelEl != null) {
            return {
                startAnchor: startAnchorEl.getBoundingClientRect(),
                endAnchor: endAnchorEl.getBoundingClientRect(),
                label: labelEl.getBoundingClientRect(),
            };
        }
        else {
            return undefined;
        }
    }
    /**
     * @ignore
     */
    connectedCallback() {
        this.setDepthBuffers();
    }
    /**
     * @ignore
     */
    disconnectedCallback() {
        var _a, _b;
        (_a = this.stateMap.hoverCursor) === null || _a === void 0 ? void 0 : _a.dispose();
        (_b = this.newInteractionHandler) === null || _b === void 0 ? void 0 : _b.dispose();
        this.resetDepthBuffers();
    }
    /**
     * @ignore
     */
    async componentWillLoad() {
        this.updatePropsFromJson();
        this.model.setMeasurementFromValues(this.start, this.end, !this.invalid);
        await this.getStencilBuffer();
        this.updateViewport();
        this.setDepthBuffers();
        await this.handleViewerChanged(this.viewer);
        await this.handleModeChanged();
        await this.computePropsAndState();
        this.model.onIndicatorChanged((pt) => {
            this.indicatorPt = pt;
        });
    }
    /**
     * @ignore
     */
    componentDidLoad() {
        const resize = new ResizeObserver(() => this.updateViewport());
        resize.observe(this.hostEl);
    }
    /**
     * @ignore
     */
    async componentWillUpdate() {
        await this.computePropsAndState();
    }
    /**
     * @ignore
     */
    render() {
        return (index.h(index.Host, { key: '58113224139c37a54bf9329ed27e6a8983d5cccd' }, this.showAxisReferenceLines && (index.h("vertex-viewer-measurement-overlays", { key: 'd2166c4a965761f472f5b3323844d82980baa29f', measurementOverlays: this.overlays, viewer: this.viewer })), this.renderMeasurement()));
    }
    renderMeasurement() {
        const positions = this.computeElementPositions();
        const { startPt, endPt, labelPt, indicatorPt, hideStart, hideEnd } = positions;
        const distance = this.formatDistance(this.distance);
        if (this.mode === 'edit' || this.mode === 'replace') {
            return (index.h(DistanceMeasurementRenderer, { startPt: startPt, endPt: endPt, centerPt: labelPt, indicatorPt: indicatorPt, distance: distance, anchorLabelOffset: this.anchorLabelOffset, lineCapLength: this.lineCapLength, hideStartAnchor: hideStart, hideEndAnchor: hideEnd, onStartAnchorPointerDown: this.handleEditAnchor('start'), onEndAnchorPointerDown: this.handleEditAnchor('end') }));
        }
        else {
            return (index.h(DistanceMeasurementRenderer, { startPt: startPt, endPt: endPt, centerPt: labelPt, indicatorPt: this.indicatorPt, distance: distance, hideStartAnchor: hideStart, hideEndAnchor: hideEnd, anchorLabelOffset: this.anchorLabelOffset, lineCapLength: this.lineCapLength, linePointerEvents: "painted" }));
        }
    }
    /**
     * @ignore
     */
    async handleViewerChanged(newViewer, oldViewer) {
        if (oldViewer != null) {
            oldViewer.removeEventListener('frameDrawn', this.handleFrameDrawn);
            await this.removeInteractionListeners(oldViewer);
        }
        if (newViewer != null) {
            newViewer.addEventListener('frameDrawn', this.handleFrameDrawn);
            await this.addInteractionListeners(newViewer);
        }
    }
    /**
     * @ignore
     */
    handleUnitsChanged() {
        this.measurementUnits = new streamAttributes.DistanceUnits(this.units);
    }
    /**
     * @ignore
     */
    handleCameraChanged() {
        this.updateCamera();
    }
    /**
     * @ignore
     */
    async handleModeChanged() {
        var _a;
        // If we're not in edit or replace mode, ensure that the measurement
        // cursor is removed.
        if (this.mode === '') {
            (_a = this.stateMap.hoverCursor) === null || _a === void 0 ? void 0 : _a.dispose();
        }
        if (this.viewer != null) {
            await this.removeInteractionListeners(this.viewer);
            await this.addInteractionListeners(this.viewer);
        }
    }
    /**
     * @ignore
     */
    handleStartChanged() {
        this.updateInteractionModel();
    }
    /**
     * @ignore
     */
    handleEndChanged() {
        this.updateInteractionModel();
    }
    /**
     * @ignore
     */
    handleInvalidChanged() {
        this.updateInteractionModel();
    }
    async computePropsAndState() {
        this.updateCamera();
        await this.updateDepthBuffer();
        this.updateMeasurementPropsFromModel();
        this.updateOverlays();
    }
    updateOverlays() {
        var _a;
        (_a = this.overlay) === null || _a === void 0 ? void 0 : _a.dispose();
        if (this.showAxisReferenceLines &&
            this.interactionCount === 0 &&
            !this.invalid &&
            this.start != null &&
            this.end != null) {
            this.overlay = this.overlays.addDistanceVector(this.start, this.end);
        }
    }
    async setCursor(cursor) {
        var _a, _b;
        (_a = this.stateMap.hoverCursor) === null || _a === void 0 ? void 0 : _a.dispose();
        if (!this.isUserInteractingWithModel) {
            this.stateMap.hoverCursor = await ((_b = this.viewer) === null || _b === void 0 ? void 0 : _b.addCursor(cursor));
        }
    }
    computeElementPositions() {
        if (this.mode === 'replace') {
            return this.computeReplaceElementPositions();
        }
        else {
            return this.computeEditOrViewElementPositions();
        }
    }
    computeEditOrViewElementPositions() {
        const measurement = this.model.getMeasurement();
        if (this.internalCamera != null && measurement != null) {
            return this.computeLineElementPositions(bundle$1.line3.create(measurement));
        }
        else {
            return {};
        }
    }
    computeReplaceElementPositions() {
        if (this.internalCamera != null) {
            const measurement = this.model.getMeasurement();
            const line = measurement != null
                ? this.computeLineElementPositions(bundle$1.line3.create(measurement))
                : {};
            const indicator = this.indicatorPt != null
                ? {
                    indicatorPt: this.viewport.transformWorldToViewport(this.indicatorPt, this.internalCamera.projectionViewMatrix),
                }
                : {};
            return { ...line, ...indicator };
        }
        else {
            return {};
        }
    }
    computeLineElementPositions(line) {
        if (this.internalCamera != null) {
            return utils.getViewingElementPositions(line, this.interactingAnchor, {
                viewport: this.viewport,
                camera: this.internalCamera,
            });
        }
        else {
            return {};
        }
    }
    updateCamera() {
        var _a, _b;
        this.internalCamera = this.camera || ((_b = (_a = this.viewer) === null || _a === void 0 ? void 0 : _a.frame) === null || _b === void 0 ? void 0 : _b.scene.camera);
    }
    async updateDepthBuffer() {
        var _a, _b;
        this.stateMap.depthBuffer = await ((_b = (_a = this.viewer) === null || _a === void 0 ? void 0 : _a.frame) === null || _b === void 0 ? void 0 : _b.depthBuffer());
    }
    updateViewport() {
        const rect = getMeasurementBoundingClientRect(this.hostEl);
        this.viewport = new viewport.Viewport(rect.width, rect.height);
        this.elementBounds = rect;
    }
    updatePropsFromJson() {
        var _a, _b;
        this.start = parseVector3((_a = this.startJson) !== null && _a !== void 0 ? _a : this.start);
        this.end = parseVector3((_b = this.endJson) !== null && _b !== void 0 ? _b : this.end);
    }
    updateInteractionModel() {
        this.model.setMeasurementFromValues(this.start, this.end, !this.invalid);
    }
    invalidateState() {
        this.invalidateStateCounter = this.invalidateStateCounter + 1;
    }
    async addInteractionListeners(viewer) {
        const interactionTarget = await viewer.getInteractionTarget_DEPRECATED();
        if (this.mode === 'replace') {
            interactionTarget.addEventListener('pointermove', this.updateStartAnchor);
            interactionTarget.addEventListener('pointerdown', this.newMeasurement);
            interactionTarget.addEventListener('pointerleave', this.clearIndicator);
        }
    }
    async removeInteractionListeners(viewer) {
        const interactionTarget = await viewer.getInteractionTarget_DEPRECATED();
        interactionTarget.removeEventListener('pointermove', this.updateStartAnchor);
        interactionTarget.removeEventListener('pointerdown', this.newMeasurement);
        interactionTarget.removeEventListener('pointerleave', this.clearIndicator);
    }
    handleEditAnchor(anchor) {
        if (this.mode === 'edit' || this.mode === 'replace') {
            const handlePointerMove = this.createInteractionMoveHandler();
            const handlePointerUp = async (event) => {
                var _a;
                const hits = this.getHitProvider();
                if (hits != null) {
                    window.removeEventListener('pointermove', handlePointerMove);
                    window.removeEventListener('pointerup', handlePointerUp);
                    const pt = dom.getMouseClientPosition(event, this.elementBounds);
                    const snapPt = this.snapPoint(pt, event);
                    await ((_a = this.interaction) === null || _a === void 0 ? void 0 : _a.finish(snapPt, hits));
                    this.updateMeasurementPropsFromModel();
                    this.endEditing();
                }
            };
            return async (event) => {
                await this.getStencilBuffer();
                if (event.button === 0) {
                    this.beginEditing('edit', anchor);
                    this.interaction = this.controller.editMeasurement(anchor);
                    window.addEventListener('pointermove', handlePointerMove);
                    window.addEventListener('pointerup', handlePointerUp);
                }
            };
        }
    }
    createInteractionMoveHandler() {
        return async (event) => {
            var _a;
            const hits = this.getHitProvider();
            if (this.elementBounds != null && hits != null) {
                event.preventDefault();
                await this.getStencilBuffer();
                const pt = dom.getMouseClientPosition(event, this.elementBounds);
                const snapPt = this.snapPoint(pt, event);
                (_a = this.interaction) === null || _a === void 0 ? void 0 : _a.update(snapPt, hits);
                this.updateMeasurementPropsFromModel();
            }
        };
    }
    async getStencilBuffer() {
        var _a;
        const stencil = await ((_a = this.viewer) === null || _a === void 0 ? void 0 : _a.stencilBuffer.latestAfterInteraction());
        this.stateMap.stencil = stencil;
    }
    snapPoint(pt, event) {
        const hits = this.getHitProvider();
        if (hits != null && !event.shiftKey) {
            const snapDistance = Math.max(0, this.snapDistance);
            return hits.hitTester().snapToNearestPixel(pt, snapDistance);
        }
        return pt;
    }
    formatDistance(distance) {
        const dist = distance != null
            ? this.measurementUnits.convertWorldValueToReal(distance)
            : undefined;
        if (this.labelFormatter != null) {
            return this.labelFormatter(dist);
        }
        else {
            const abbreviated = this.measurementUnits.unit.abbreviatedName;
            return dist == null
                ? '---'
                : `~${dist.toFixed(this.fractionalDigits)} ${abbreviated}`;
        }
    }
    beginEditing(type, anchor) {
        if (this.interactionCount === 0) {
            this.interactingAnchor = anchor;
            this.editBegin.emit({ type, anchor });
        }
        this.interactionCount = this.interactionCount + 1;
    }
    endEditing() {
        if (this.interactionCount === 1) {
            const measurement = this.model.getMeasurement();
            this.interactingAnchor = 'none';
            this.updateMeasurementModel();
            if (measurement != null) {
                this.editEnd.emit(measurement);
            }
        }
        this.interactionCount = this.interactionCount - 1;
    }
    updateMeasurementModel() {
        this.measurementModel.clearOutcome();
        if (!this.invalid && this.start != null && this.end != null) {
            this.measurementModel.setOutcome({
                isApproximate: true,
                results: [results.makeMinimumDistanceResult(this.start, this.end)],
            });
        }
    }
    getHitProvider() {
        if (this.hitProvider == null) {
            const hitTester = this.getHitTester();
            const viewer = this.viewer;
            if (viewer != null && hitTester != null) {
                return {
                    hitTester: () => hitTester,
                    raycaster: async () => (await viewer.scene()).raycaster(),
                };
            }
        }
        else
            return this.hitProvider;
    }
    getHitTester() {
        const { stencil, depthBuffer } = this.stateMap;
        if (depthBuffer != null) {
            return new PointToPointHitTester(stencil, depthBuffer, this.viewport, this.internalCamera);
        }
    }
    updateMeasurementPropsFromModel() {
        const measurement = this.model.getMeasurement();
        this.start = measurement === null || measurement === void 0 ? void 0 : measurement.start;
        this.end = measurement === null || measurement === void 0 ? void 0 : measurement.end;
        this.distance = measurement === null || measurement === void 0 ? void 0 : measurement.distance;
        this.invalid = measurement != null && !measurement.valid;
    }
    async updateIndicator(pt) {
        var _a;
        const hits = this.getHitProvider();
        const clearCursor = hits == null || !this.controller.moveIndicator(pt, hits);
        if (clearCursor) {
            (_a = this.stateMap.hoverCursor) === null || _a === void 0 ? void 0 : _a.dispose();
        }
        else {
            await this.setCursor(cursors.measurementCursor);
        }
    }
    setDepthBuffers() {
        if (this.mode != null &&
            this.viewer != null &&
            this.viewer.depthBuffers == null) {
            this.stateMap.shouldClearDepthBuffers = true;
            this.viewer.depthBuffers = 'final';
        }
    }
    resetDepthBuffers() {
        if (this.stateMap.shouldClearDepthBuffers && this.viewer != null) {
            this.viewer.depthBuffers = undefined;
            this.stateMap.shouldClearDepthBuffers = undefined;
        }
    }
    get hostEl() { return index.getElement(this); }
    static get watchers() { return {
        "viewer": [{
                "handleViewerChanged": 0
            }],
        "units": [{
                "handleUnitsChanged": 0
            }],
        "camera": [{
                "handleCameraChanged": 0
            }],
        "mode": [{
                "handleModeChanged": 0
            }],
        "start": [{
                "handleStartChanged": 0
            }],
        "end": [{
                "handleEndChanged": 0
            }],
        "invalid": [{
                "handleInvalidChanged": 0
            }]
    }; }
};
function parseVector3(value) {
    return typeof value === 'string' ? bundle$1.vector3.fromJson(value) : value;
}
ViewerMeasurementDistance.style = viewerMeasurementDistanceCss();

exports.vertex_viewer_measurement_distance = ViewerMeasurementDistance;
//# sourceMappingURL=vertex-viewer-measurement-distance.entry.cjs.js.map

//# sourceMappingURL=vertex-viewer-measurement-distance.cjs.entry.js.map