/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Curve
 */
import { Geometry } from "../Geometry";
import { Point3d } from "../geometry3d/Point3dVector3d";
/**
 * An enumeration of special conditions being described by a CurveLocationDetail.
 * @public
 */
export var CurveIntervalRole;
(function (CurveIntervalRole) {
    /** This point is an isolated point NOT at a primary vertex. */
    CurveIntervalRole[CurveIntervalRole["isolated"] = 0] = "isolated";
    /** This point is an isolated vertex hit */
    CurveIntervalRole[CurveIntervalRole["isolatedAtVertex"] = 1] = "isolatedAtVertex";
    /** This is the beginning of an interval */
    CurveIntervalRole[CurveIntervalRole["intervalStart"] = 10] = "intervalStart";
    /** This is an interior point of an interval. */
    CurveIntervalRole[CurveIntervalRole["intervalInterior"] = 11] = "intervalInterior";
    /** This is the end of an interval */
    CurveIntervalRole[CurveIntervalRole["intervalEnd"] = 12] = "intervalEnd";
})(CurveIntervalRole || (CurveIntervalRole = {}));
/**
 * Return code for CurvePrimitive method `moveSignedDistanceFromFraction`
 * @public
 */
export var CurveSearchStatus;
(function (CurveSearchStatus) {
    /** Unimplemented or zero length curve  */
    CurveSearchStatus[CurveSearchStatus["error"] = 0] = "error";
    /** Complete success of search */
    CurveSearchStatus[CurveSearchStatus["success"] = 1] = "success";
    /** Search ended prematurely (e.g. at incomplete distance moved) at start or end of curve */
    CurveSearchStatus[CurveSearchStatus["stoppedAtBoundary"] = 2] = "stoppedAtBoundary";
})(CurveSearchStatus || (CurveSearchStatus = {}));
/**
 * Use to update a cloneable object when source and/or prior result are possibly undefined.
 * * Any undefined source returns undefined.
 * * For defined source, reuse optional result if available.
 * @param source optional source
 * @param result optional result
 */
function optionalUpdate(source, result) {
    return source ? source.clone(result) : undefined;
}
/**
 * CurveLocationDetail carries point and parameter data about a point evaluated on a curve.
 * * These are returned by a variety of queries.
 * * Particular contents can vary among the queries.
 * @public
 */
export class CurveLocationDetail {
    /** The curve being evaluated. */
    curve;
    /** Optional ray. */
    ray;
    /** The fractional position along the curve. */
    fraction;
    /** Detail condition of the role this point has in some context. */
    intervalRole;
    /** The point on the curve. */
    point;
    /** A vector (e.g. tangent vector) in context. */
    vectorInCurveLocationDetail;
    /** A context-specific numeric value (e.g., a distance). */
    a;
    /**
     * Optional CurveLocationDetail with more detail of location. For instance, a detail for fractional position
     * within a CurveChainWithDistanceIndex returns fraction and distance along the chain as its primary data and
     * further detail of the particular curve within the chain in the childDetail.
     */
    childDetail;
    /**
     * A status indicator for certain searches.
     * * e.g., CurvePrimitive.moveSignedDistanceFromFraction.
     */
    curveSearchStatus;
    /** (Optional) second fraction, e.g. end of interval of coincident curves. */
    fraction1;
    /** (Optional) second point, e.g. end of interval of coincident curves. */
    point1;
    /** A context-specific temporary point, e.g. for intermediate calculations. */
    pointQ;
    /** Constructor */
    constructor() {
        this.pointQ = Point3d.createZero();
        this.fraction = 0;
        this.point = Point3d.createZero();
        this.a = 0.0;
    }
    /** Set the (optional) intervalRole field. */
    setIntervalRole(value) {
        this.intervalRole = value;
    }
    /** Set the `fraction` and `point`, using direct assignment (capture!) to `point`. */
    captureFractionPoint(fraction, point) {
        this.fraction = fraction;
        this.point = point;
    }
    /** Set the (optional) `fraction1` and `point1`, using direct assignment (capture!) to `point1`. */
    captureFraction1Point1(fraction1, point1) {
        this.fraction1 = fraction1;
        this.point1 = point1;
    }
    /** Test if this pair has fraction1 defined. */
    get hasFraction1() {
        return this.fraction1 !== undefined;
    }
    /** Test if this is an isolated point. This is true if intervalRole is any of (undefined, isolated, isolatedAtVertex). */
    get isIsolated() {
        return this.intervalRole === undefined
            || this.intervalRole === CurveIntervalRole.isolated
            || this.intervalRole === CurveIntervalRole.isolatedAtVertex;
    }
    /** Return the fraction delta. (0 if no fraction1). */
    get fractionDelta() {
        return this.fraction1 !== undefined ? this.fraction1 - this.fraction : 0.0;
    }
    /**
     * If (fraction1, point1) are defined, make them the primary (and only) data.
     * * No action if undefined.
     */
    collapseToEnd() {
        if (this.fraction1 !== undefined)
            this.fraction = this.fraction1;
        if (this.point1)
            this.point = this.point1;
        this.collapseToStart();
    }
    /** Make (fraction, point) the primary (and only) data. */
    collapseToStart() {
        this.fraction1 = undefined;
        this.point1 = undefined;
    }
    /**
     * Return a complete copy, WITH CAVEATS.
     * * curve member is copied as a reference.
     * * point and vector members are cloned.
     */
    clone(result) {
        if (result === this)
            return result;
        result = result ? result : new CurveLocationDetail();
        result.curve = this.curve;
        result.fraction = this.fraction;
        result.fraction1 = this.fraction1;
        result.point1 = optionalUpdate(this.point1, result.point1);
        result.point.setFromPoint3d(this.point);
        result.vectorInCurveLocationDetail = optionalUpdate(this.vectorInCurveLocationDetail, result.vectorInCurveLocationDetail);
        result.a = this.a;
        result.childDetail = optionalUpdate(this.childDetail, result.childDetail);
        result.curveSearchStatus = this.curveSearchStatus;
        return result;
    }
    /**
     * Updated in this instance.
     * * Note that if caller omits `vector` and `a`, those fields are updated to the call-list defaults (NOT left as-is)
     * * point and vector updates are by data copy (not capture of pointers).
     * @param fraction (required) fraction to install.
     * @param point (required) point to install.
     * @param vector (optional) vector to install.
     * @param a (optional) numeric value to install.
     */
    setFP(fraction, point, vector, a = 0.0) {
        this.fraction = fraction;
        this.point.setFromPoint3d(point);
        this.vectorInCurveLocationDetail = optionalUpdate(vector, this.vectorInCurveLocationDetail);
        this.a = a;
    }
    /**
     * Updated in this instance.
     * * Note that if caller omits a`, that field is updated to the call-list default (NOT left as-is)
     * * point and vector updates are by data copy (not capture of the ray members).
     * @param fraction (required) fraction to install.
     * @param ray  (required) point and vector to install.
     * @param a (optional) numeric value to install.
     */
    setFR(fraction, ray, a = 0) {
        return this.setFP(fraction, ray.origin, ray.direction, a);
    }
    /** Set the CurvePrimitive pointer, leaving all other properties untouched. */
    setCurve(curve) {
        this.curve = curve;
    }
    /** Record the distance from the CurveLocationDetail's point to the parameter point. */
    setDistanceTo(point) {
        this.a = this.point.distance(point);
    }
    /** Create with a CurvePrimitive pointer but no coordinate data. */
    static create(curve, result) {
        result = result ? result : new CurveLocationDetail();
        result.curve = curve;
        return result;
    }
    /** Create a new detail using CurvePrimitive pointer, fraction, and point coordinates. */
    static createCurveFractionPoint(curve, fraction, point, result) {
        result = result ? result : new CurveLocationDetail();
        result.curve = curve;
        result.fraction = fraction;
        result.point.setFromPoint3d(point);
        result.vectorInCurveLocationDetail = undefined;
        result.a = 0.0;
        result.childDetail = undefined;
        result.curveSearchStatus = undefined;
        return result;
    }
    /** Create a new detail with only ray, fraction, and point. */
    static createRayFractionPoint(ray, fraction, point, result) {
        result = result ? result : new CurveLocationDetail();
        result.fraction = fraction;
        result.ray = ray;
        result.point.setFromPoint3d(point);
        return result;
    }
    /** Create with CurvePrimitive pointer, fraction, and point coordinates. */
    static createCurveFractionPointDistanceCurveSearchStatus(curve, fraction, point, distance, status, result) {
        result = result ? result : new CurveLocationDetail();
        result.curve = curve;
        result.fraction = fraction;
        result.point.setFromPoint3d(point);
        result.vectorInCurveLocationDetail = undefined;
        result.a = distance;
        result.childDetail = undefined;
        result.curveSearchStatus = status;
        return result;
    }
    /** Create with curveSearchStatus affected by allowExtension. */
    static createConditionalMoveSignedDistance(allowExtension, curve, startFraction, endFraction, requestedSignedDistance, result) {
        let a = requestedSignedDistance;
        let status = CurveSearchStatus.success;
        if (!allowExtension && !Geometry.isIn01(endFraction)) {
            // cap the movement at the endpoint
            if (endFraction < 0.0) {
                a = -curve.curveLengthBetweenFractions(startFraction, 0.0);
                endFraction = 0.0;
                status = CurveSearchStatus.stoppedAtBoundary;
            }
            else if (endFraction > 1.0) {
                endFraction = 1.0;
                a = curve.curveLengthBetweenFractions(startFraction, 1.0);
                status = CurveSearchStatus.stoppedAtBoundary;
            }
        }
        result = result ? result : new CurveLocationDetail();
        result.curve = curve;
        result.fraction = endFraction;
        curve.fractionToPoint(endFraction, result.point);
        result.vectorInCurveLocationDetail = undefined;
        result.a = a;
        result.childDetail = undefined;
        result.curveSearchStatus = status;
        return result;
    }
    /** Create with CurvePrimitive pointer and fraction for evaluation. */
    static createCurveEvaluatedFraction(curve, fraction, result) {
        result = result ? result : new CurveLocationDetail();
        result.curve = curve;
        result.fraction = fraction;
        curve.fractionToPoint(fraction, result.point);
        result.vectorInCurveLocationDetail = undefined;
        result.a = 0.0;
        result.childDetail = undefined;
        result.curveSearchStatus = undefined;
        return result;
    }
    /** Create with CurvePrimitive pointer and fraction for evaluation. */
    static createCurveEvaluatedFractionPointAndDerivative(curve, fraction, result) {
        result = result ? result : new CurveLocationDetail();
        result.curve = curve;
        result.fraction = fraction;
        const ray = curve.fractionToPointAndDerivative(fraction);
        result.point.setFromPoint3d(ray.origin);
        result.vectorInCurveLocationDetail = ray.direction;
        result.a = 0.0;
        result.childDetail = undefined;
        result.curveSearchStatus = undefined;
        return result;
    }
    /** Create with CurvePrimitive pointer and 2 fractions for evaluation. */
    static createCurveEvaluatedFractionFraction(curve, fraction0, fraction1, result) {
        result = result ? result : new CurveLocationDetail();
        result.curve = curve;
        result.fraction = fraction0;
        curve.fractionToPoint(fraction0, result.point);
        result.fraction1 = fraction1;
        result.point1 = curve.fractionToPoint(fraction1, result.point1);
        result.vectorInCurveLocationDetail = undefined;
        result.a = 0.0;
        result.childDetail = undefined;
        result.curveSearchStatus = undefined;
        return result;
    }
    /** Create with CurvePrimitive pointer, fraction, and point coordinates. */
    static createCurveFractionPointDistance(curve, fraction, point, a, result) {
        result = result ? result : new CurveLocationDetail();
        result.curve = curve;
        result.fraction = fraction;
        result.point.setFromPoint3d(point);
        result.vectorInCurveLocationDetail = undefined;
        result.a = a;
        result.childDetail = undefined;
        result.curveSearchStatus = undefined;
        return result;
    }
    /**
     * Update or create if closer than current contents.
     * @param curve candidate curve
     * @param fraction candidate fraction
     * @param point candidate point
     * @param a candidate distance
     * @returns true if the given distance is smaller (and hence this detail was updated)
     */
    updateIfCloserCurveFractionPointDistance(curve, fraction, point, a) {
        if (this.a < a)
            return false;
        CurveLocationDetail.createCurveFractionPointDistance(curve, fraction, point, a, this);
        return true;
    }
    /**
     * Exchange the (fraction,fraction1) and (point, point1) pairs.
     * * (Skip each swap if its "1" value is undefined)
     */
    swapFractionsAndPoints() {
        if (this.fraction1 !== undefined) {
            const f = this.fraction;
            this.fraction = this.fraction1;
            this.fraction1 = f;
        }
        if (this.point1 !== undefined) {
            const p = this.point;
            this.point = this.point1;
            this.point1 = p;
        }
    }
    /**
     * Return the fraction where f falls between fraction and fraction1.
     * * ASSUME fraction1 defined
     */
    inverseInterpolateFraction(f, defaultFraction = 0) {
        const a = Geometry.inverseInterpolate01(this.fraction, this.fraction1, f);
        if (a === undefined)
            return defaultFraction;
        return a;
    }
    /**
     * Return the detail with smaller `a` value -- detailA returned if equal.
     * @param detailA first candidate
     * @param detailB second candidate
     */
    static chooseSmallerA(detailA, detailB) {
        if (detailA) {
            if (!detailB)
                return detailA;
            return detailA.a <= detailB.a ? detailA : detailB;
        }
        return detailB;
    }
    /** Compare only the curve and fraction of this detail with `other`. */
    isSameCurveAndFraction(other) {
        return this.curve === other.curve && Geometry.isAlmostEqualNumber(this.fraction, other.fraction);
    }
    /**
     * Transform the detail in place.
     * * All numerical properties are transformed directly, except:
     *   * when `curve` is defined, `point` and `point1` are reset by evaluating the transformed curve at `fraction` and `fraction1`
     *   * these properties are untouched: `a`, `fraction`, `fraction1`, `pointQ`
     * @param transform the transform to apply
     * @return true if and only if the transformation was successful. If false, the instance is in an indeterminate
     * state and should not be used.
     */
    tryTransformInPlace(transform) {
        if (this.curve && !this.curve.tryTransformInPlace(transform))
            return false;
        if (this.ray)
            this.ray.transformInPlace(transform);
        if (this.curve)
            this.curve.fractionToPoint(this.fraction, this.point);
        else
            transform.multiplyXYAndZInPlace(this.point);
        if (this.vectorInCurveLocationDetail)
            transform.multiplyVectorInPlace(this.vectorInCurveLocationDetail);
        if (this.childDetail && this.childDetail !== this && !this.childDetail.tryTransformInPlace(transform))
            return false;
        if (this.point1) {
            if (this.curve && this.fraction1)
                this.curve.fractionToPoint(this.fraction1, this.point1);
            else
                transform.multiplyXYAndZInPlace(this.point1);
        }
        return true;
    }
}
/**
 * Enumeration of configurations for intersections and min/max distance-between-curve
 * @public
 */
export var CurveCurveApproachType;
(function (CurveCurveApproachType) {
    /** Intersection at a single point */
    CurveCurveApproachType[CurveCurveApproachType["Intersection"] = 0] = "Intersection";
    /** Distinct points on the two curves, with each curve's tangent perpendicular to the chord between the points */
    CurveCurveApproachType[CurveCurveApproachType["PerpendicularChord"] = 1] = "PerpendicularChord";
    /** Completely coincident geometry */
    CurveCurveApproachType[CurveCurveApproachType["CoincidentGeometry"] = 2] = "CoincidentGeometry";
    /** Completely parallel geometry. */
    CurveCurveApproachType[CurveCurveApproachType["ParallelGeometry"] = 3] = "ParallelGeometry";
})(CurveCurveApproachType || (CurveCurveApproachType = {}));
/**
 * A pair of CurveLocationDetail.
 * @public
 */
export class CurveLocationDetailPair {
    /** The first of the two details. */
    detailA;
    /** The second of the two details. */
    detailB;
    /**
     * Enumeration of how the detail pairs relate.
     * * This is set only by certain closeApproach calculations.
     */
    approachType;
    constructor(detailA, detailB) {
        this.detailA = detailA ? detailA : new CurveLocationDetail();
        this.detailB = detailB ? detailB : new CurveLocationDetail();
    }
    /** Create a curve detail pair using references to two CurveLocationDetails */
    static createCapture(detailA, detailB, result) {
        result = result ? result : new CurveLocationDetailPair();
        result.detailA = detailA;
        result.detailB = detailB;
        return result;
    }
    /**
     * Create a curve detail pair using references to two CurveLocationDetails.
     * * optionally install in reversed positions
     */
    static createCaptureOptionalReverse(detailA, detailB, reversed, result) {
        result = result ? result : new CurveLocationDetailPair();
        if (reversed) {
            result.detailA = detailA;
            result.detailB = detailB;
        }
        else {
            result.detailA = detailA;
            result.detailB = detailB;
        }
        return result;
    }
    /** Make a deep copy of this CurveLocationDetailPair */
    clone(result) {
        result = result ? result : new CurveLocationDetailPair();
        result.detailA = this.detailA.clone();
        result.detailB = this.detailB.clone();
        result.approachType = this.approachType;
        return result;
    }
    /** Swap the details of A, B */
    swapDetails() {
        const q = this.detailA;
        this.detailA = this.detailB;
        this.detailB = q;
    }
    /**
     * Mutate the input array by removing the second of two adjacent duplicate pairs.
     * * Ignores details representing coincident intervals (e.g., for which `fraction1` is defined).
     * * Comparison is performed by [[CurveLocationDetail.isSameCurveAndFraction]].
     * * No sorting is performed.
     * @param pairs array to de-duplicate in place
     * @param index0 look for duplicates in the tail of the array starting at index0
     * @return reference to input array
     * @internal
     */
    static removeAdjacentDuplicates(pairs, index0 = 0) {
        return pairs.flatMap((pair, i, arr) => {
            if (i >= index0 && i > 0) {
                if (!pair.detailA.hasFraction1 && !pair.detailB.hasFraction1) {
                    if (pair.detailA.isSameCurveAndFraction(arr[i - 1].detailA)) {
                        if (pair.detailB.isSameCurveAndFraction(arr[i - 1].detailB)) {
                            return []; // remove the i_th pair
                        }
                    }
                }
            }
            return [pair]; // preserve the i_th pair
        });
    }
    /**
     * Transform the details in place.
     * @param transform the transform to apply
     * @return true if and only if the transformation was successful on both details.
     * If false, the instance is in an indeterminate state and should not be used.
     */
    tryTransformInPlace(transform) {
        return this.detailA.tryTransformInPlace(transform) && this.detailB.tryTransformInPlace(transform);
    }
}
/**
 * Data bundle for a pair of arrays of CurveLocationDetail structures.
 * @deprecated in 4.2.0 - will not be removed until after 2026-06-13. Use CurveLocationDetailPair[] instead.
 * @public
 */
export class CurveLocationDetailArrayPair {
    /** First array of details. */
    dataA;
    /** Second array of details. */
    dataB;
    constructor() {
        this.dataA = [];
        this.dataB = [];
    }
}
//# sourceMappingURL=CurveLocationDetail.js.map