"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CurveChainWithDistanceIndex = exports.PathFragment = void 0;
/** @packageDocumentation
 * @module Curve
 */
const core_bentley_1 = require("@itwin/core-bentley");
const CurvePrimitive_1 = require("../curve/CurvePrimitive");
const StrokeCountMap_1 = require("../curve/Query/StrokeCountMap");
const Geometry_1 = require("../Geometry");
const Point3dVector3d_1 = require("../geometry3d/Point3dVector3d");
const Range_1 = require("../geometry3d/Range");
const CurveExtendMode_1 = require("./CurveExtendMode");
const CurveLocationDetail_1 = require("./CurveLocationDetail");
const PlaneAltitudeRangeContext_1 = require("./internalContexts/PlaneAltitudeRangeContext");
const OffsetOptions_1 = require("./OffsetOptions");
const Path_1 = require("./Path");
/**
 * Annotation of a fragment, i.e. an interval of a curve.
 * * The interval is marked with two pairs of numbers:
 * * * fraction0, fraction1 = fraction parameters along the child curve.
 * * * distance0, distance1 = distances within containing CurveChainWithDistanceIndex.
 * @public
 */
class PathFragment {
    /** Distance along parent to this fragment start. */
    chainDistance0;
    /** Distance along parent to this fragment end. */
    chainDistance1;
    /** The start of this `PathFragment`, as a local fractional parameter of `this.childCurve`. */
    childFraction0;
    /** The end of this `PathFragment`, as a local fractional parameter of `this.childCurve`. */
    childFraction1;
    /** Curve primitive of this fragment, as presented in stroker. Note that this might have become a proxy. */
    childCurve;
    /** Optional range */
    range;
    /** Working var for use in searches. */
    a;
    /** Create a fragment with complete fraction, distance, and child data. */
    constructor(childFraction0, childFraction1, distance0, distance1, childCurve, range) {
        this.childFraction0 = childFraction0;
        this.childFraction1 = childFraction1;
        this.chainDistance0 = distance0;
        this.chainDistance1 = distance1;
        this.childCurve = childCurve;
        this.range = range;
        this.a = 0;
    }
    /** Return true if the distance is within the distance limits of this fragment. */
    containsChainDistance(distance) {
        return distance >= this.chainDistance0 && distance <= this.chainDistance1;
    }
    /**
     * Return a quick minimum distance from spacePoint to the curve.
     * * The returned distance is to the curve's range box if defined; otherwise, the true distance is computed.
     * * Thus the returned distance may be SMALLER than the true distance to the curve, but not larger.
     */
    quickMinDistanceToChildCurve(spacePoint) {
        if (this.range)
            return this.range.distanceToPoint(spacePoint);
        const detail = this.childCurve.closestPoint(spacePoint, false);
        if (detail)
            return detail.a;
        return 0;
    }
    /**
     * Return an array with (references to) all the input path fragments, sorted smallest to largest on the "a" value,
     * initialized with `quickMinDistanceToChildCurve`
     */
    static collectSortedQuickMinDistances(fragments, spacePoint) {
        const sortedFragments = [];
        for (const frag of fragments) {
            frag.a = frag.quickMinDistanceToChildCurve(spacePoint);
            sortedFragments.push(frag);
        }
        sortedFragments.sort((frag1, frag2) => frag1.a - frag2.a);
        return sortedFragments;
    }
    /** Return true if `this` fragment addresses `curve` and brackets `fraction`. */
    containsChildCurveAndChildFraction(curve, fraction) {
        return this.childCurve === curve && fraction >= this.childFraction0 && fraction <= this.childFraction1;
    }
    /**
     * Convert distance to local fraction and apply that to interpolate between the stored curve fractions.
     * Note that proportional calculation does NOT account for non-uniform parameterization in the child curve.
     */
    chainDistanceToInterpolatedChildFraction(distance) {
        return Geometry_1.Geometry.inverseInterpolate(this.childFraction0, this.chainDistance0, this.childFraction1, this.chainDistance1, distance, this.childFraction0); // the interval must have nonzero length so division should be safe
    }
    /**
     * Convert the given chainDistance to a fraction along this childCurve using `moveSignedDistanceFromFraction`.
     * @param chainDistance signed distance along the chain.
     * @param allowExtrapolation whether to extrapolate off the start of the chain if `chainDistance` is negative,
     * or off the end of the chain if `chainDistance` is more than `chainLength`. Extending the chain in this manner
     * may result in significant inaccuracy, especially for negative distances.
     * @return fraction local to the relevant chain primitive corresponding to the chain position at `chainDistance`.
     */
    chainDistanceToAccurateChildFraction(chainDistance, allowExtrapolation) {
        const childDetail = this.childCurve.moveSignedDistanceFromFraction(this.childFraction0, chainDistance - this.chainDistance0, allowExtrapolation ?? false);
        return childDetail.fraction;
    }
    /**
     * Return the scale factor to map childCurve fraction derivatives to chain fraction derivatives.
     * @param globalDistance total length of the global curve
     */
    fractionScaleFactor(globalDistance) {
        return globalDistance * (this.childFraction1 - this.childFraction0) / (this.chainDistance1 - this.chainDistance0);
    }
    /**
     * Reverse the fraction and distance data.
     * * Each child fraction `f` is replaced by `1-f`
     * * Each `chainDistance` is replaced by `totalDistance - chainDistance`
     * @param totalDistance the total distance
     */
    reverseFractionsAndDistances(totalDistance) {
        const f0 = this.childFraction0;
        const f1 = this.childFraction1;
        const d0 = this.chainDistance0;
        const d1 = this.chainDistance1;
        this.childFraction0 = 1.0 - f1;
        this.childFraction1 = 1.0 - f0;
        this.chainDistance0 = totalDistance - d1;
        this.chainDistance1 = totalDistance - d0;
    }
    /**
     * Convert a fractional position on the childCurve of this fragment to distance on the curve chain.
     * * Return value is SIGNED and will be negative when `fraction < this.childFraction0`.
     * @param fraction the fractional position on the childCurve of this fragment
     */
    childFractionToChainDistance(fraction) {
        let d = this.childCurve.curveLengthBetweenFractions(this.childFraction0, fraction);
        if (fraction < this.childFraction0)
            d = -d;
        return this.chainDistance0 + d;
    }
}
exports.PathFragment = PathFragment;
/** Non-instantiable class to build a distance index for a curve chain. */
class DistanceIndexConstructionContext {
    _fragments;
    _accumulatedDistance;
    constructor() {
        this._accumulatedDistance = 0;
        this._fragments = [];
    }
    // ignore curve announcements -- they are repeated in stroke announcements
    startParentCurvePrimitive(_cp) { }
    startCurvePrimitive(_cp) { }
    endParentCurvePrimitive(_cp) { }
    endCurvePrimitive(_cp) { }
    announcePointTangent(_xyz, _fraction, _tangent) { }
    announceSegmentInterval(cp, point0, point1, numStrokes, fraction0, fraction1) {
        const fragmentPoint0 = point0.clone();
        const fragmentPoint1 = point1.clone();
        let d0 = this._accumulatedDistance;
        if (numStrokes <= 1) {
            this._accumulatedDistance += point0.distance(point1);
            this._fragments.push(new PathFragment(fraction0, fraction1, d0, this._accumulatedDistance, cp, Range_1.Range3d.create(fragmentPoint0, fragmentPoint1)));
        }
        else {
            let f1;
            for (let i = 1, f0 = fraction0; i <= numStrokes; i++, f0 = f1) {
                f1 = Geometry_1.Geometry.interpolate(fraction0, i / numStrokes, fraction1);
                point0.interpolate(f1, point1, fragmentPoint1);
                d0 = this._accumulatedDistance;
                this._accumulatedDistance += (Math.abs(f1 - f0) * point0.distance(point1));
                this._fragments.push(new PathFragment(f0, f1, d0, this._accumulatedDistance, cp, Range_1.Range3d.create(fragmentPoint0, fragmentPoint1)));
                fragmentPoint0.setFrom(fragmentPoint1);
            }
        }
    }
    announceIntervalForUniformStepStrokes(cp, numStrokes, fraction0, fraction1) {
        let f1, d, d0;
        for (let i = 1, f0 = fraction0; i <= numStrokes; i++, f0 = f1) {
            f1 = Geometry_1.Geometry.interpolate(fraction0, i / numStrokes, fraction1);
            d = cp.curveLengthBetweenFractions(f0, f1);
            d0 = this._accumulatedDistance;
            this._accumulatedDistance += d;
            const range = cp.rangeBetweenFractions(f0, f1);
            this._fragments.push(new PathFragment(f0, f1, d0, this._accumulatedDistance, cp, range));
        }
    }
    needPrimaryGeometryForStrokes() {
        return true;
    }
    /** Create an array of PathFragment from input curve chain. */
    static createPathFragmentIndex(path, options) {
        const handler = new DistanceIndexConstructionContext();
        for (const curve of path.children) {
            curve.emitStrokableParts(handler, options);
        }
        const fragments = handler._fragments;
        return fragments;
    }
}
/**
 * `CurveChainWithDistanceIndex` is a [[CurvePrimitive]] whose fractional parameterization is proportional to true
 * distance along a path.
 * * For example if the total length of the chain is `L`, then the distance along the chain from parameters `t0`
 * to `t1` is easily computed as `L*(t1-t0)`.
 * * In this way the `CurveChainWithDistanceIndex` provides a global arc length parameterization for a [[CurveChain]].
 * * A `CurveChainWithDistanceIndex` can be created from any [[CurveChain]], but a [[Path]] is stored internally.
 * * Adding a `CurveChainWithDistanceIndex` to a `CurveChain` does not preserve the distance index; instead the
 * internal path children are added directly to the `CurveChain`.
 * @public
 */
class CurveChainWithDistanceIndex extends CurvePrimitive_1.CurvePrimitive {
    /** String name for schema properties */
    curvePrimitiveType = "curveChainWithDistanceIndex";
    _path;
    _fragments;
    _totalLength; // matches final fragment distance1.
    static _numCalls = 0;
    static _numTested = 0;
    static _numAssigned = 0;
    static _numCandidate = 0;
    /** Test if `other` is a `CurveChainWithDistanceIndex` */
    isSameGeometryClass(other) {
        return other instanceof CurveChainWithDistanceIndex;
    }
    // final assembly of CurveChainWithDistanceIndex -- caller must create valid fragment index.
    constructor(path, fragments) {
        super();
        this._path = path instanceof Path_1.Path ? path : Path_1.Path.create(...path.children); // Loop semantics would be confusing
        this._fragments = fragments;
        this._totalLength = fragments.length > 0 ? fragments[fragments.length - 1].chainDistance1 : 0;
    }
    /**
     * Create a clone, transformed and with its own distance index.
     * @param transform transform to apply in the clone.
     * @param options how finely to stroke the path to create the distance index
     */
    cloneTransformed(transform, options) {
        const c = this._path.clone();
        if (c.tryTransformInPlace(transform))
            return CurveChainWithDistanceIndex.createCapture(c, options);
        return undefined;
    }
    /**
     * Reference to the contained path.
     * * Do not modify the path. The distance index will be wrong.
     */
    get path() {
        return this._path;
    }
    /**
     * Reference to the fragments array.
     * * Do not modify.
     */
    get fragments() {
        return this._fragments;
    }
    /**
     * Return a deep clone with its own distance index.
     * @param options how finely to stroke the path to create the distance index
     */
    clone(options) {
        const c = this._path.clone();
        return CurveChainWithDistanceIndex.createCapture(c, options);
    }
    /**
     * Return a portion of this curve with its own distance index.
     * * Passing a chain detail containing a `childDetail` is more accurate for extending the instance beyond [0,1].
     * Such details are returned by e.g., [[CurveCurve.intersectionXYPairs]].
     * @param fractionA start fraction or chain detail
     * @param fractionB end fraction or chain detail
     * @param options how finely to stroke the path to create the distance index
     */
    clonePartialCurve(fractionA, fractionB, options) {
        const haveDetailA = fractionA instanceof CurveLocationDetail_1.CurveLocationDetail;
        const haveDetailB = fractionB instanceof CurveLocationDetail_1.CurveLocationDetail;
        let chainFractionA = haveDetailA ? fractionA.fraction : fractionA;
        let chainFractionB = haveDetailB ? fractionB.fraction : fractionB;
        if (chainFractionA === chainFractionB)
            return undefined;
        const reversed = chainFractionA > chainFractionB;
        if (reversed)
            [chainFractionA, chainFractionB] = [chainFractionB, chainFractionA];
        const chainDistanceA = chainFractionA * this._totalLength;
        const chainDistanceB = chainFractionB * this._totalLength;
        const fragmentA = this.chainDistanceToFragment(chainDistanceA, true);
        if (undefined === fragmentA)
            return undefined;
        const fragmentB = this.chainDistanceToFragment(chainDistanceB, true);
        if (undefined === fragmentB)
            return undefined;
        const childCurveIndexA = this._path.childIndex(fragmentA.childCurve, true);
        if (undefined === childCurveIndexA)
            return undefined;
        const childCurveIndexB = this._path.childIndex(fragmentB.childCurve, true);
        if (undefined === childCurveIndexB)
            return undefined;
        const childFractionA = haveDetailA && fractionA.childDetail ? fractionA.childDetail.fraction : fragmentA.chainDistanceToAccurateChildFraction(chainDistanceA, true);
        const childFractionB = haveDetailB && fractionB.childDetail ? fractionB.childDetail.fraction : fragmentB.chainDistanceToAccurateChildFraction(chainDistanceB, true);
        // add a (possibly reversed) partial clone to newPath
        const newPath = Path_1.Path.create();
        const addPartialChild = (childCurve, childFraction0, childFraction1, reversedClone) => {
            if (childFraction0 === childFraction1)
                return false;
            let newCurve;
            if (childFraction0 === 0.0 && childFraction1 === 1.0) {
                newCurve = childCurve.clone();
                if (reversedClone)
                    newCurve.reverseInPlace();
            }
            else {
                newCurve = reversedClone ?
                    childCurve.clonePartialCurve(childFraction1, childFraction0)
                    : childCurve.clonePartialCurve(childFraction0, childFraction1);
            }
            if (newCurve) {
                newPath.children.push(newCurve);
                return true;
            }
            return false;
        };
        if (fragmentA.childCurve === fragmentB.childCurve) {
            // the two distances are within the same curve.
            if (addPartialChild(fragmentA.childCurve, childFractionA, childFractionB, reversed))
                return CurveChainWithDistanceIndex.createCapture(newPath, options); // singleton -- children[] does not need to be reversed.
            return undefined;
        }
        addPartialChild(this._path.children[childCurveIndexA], childFractionA, 1.0, reversed);
        // at least two distinct children are impacted ....
        for (let childIndex = childCurveIndexA + 1; childIndex < childCurveIndexB; childIndex++) {
            addPartialChild(this._path.children[childIndex], 0.0, 1.0, reversed);
        }
        addPartialChild(this._path.children[childCurveIndexB], 0.0, childFractionB, reversed);
        // This reverses array entries but not orientation within each curve ...
        if (reversed)
            newPath.children.reverse();
        return CurveChainWithDistanceIndex.createCapture(newPath, options);
    }
    /**
     * Ask if the curve is within tolerance of a plane.
     * @returns Returns true if the curve is completely within tolerance of the plane.
     */
    isInPlane(plane) {
        for (const c of this._path.children) {
            if (!c.isInPlane(plane))
                return false;
        }
        return true;
    }
    /** Return the start point of `this` curve. */
    startPoint(result) {
        const c = this._path.cyclicCurvePrimitive(0);
        if (c)
            return c.startPoint(result);
        return Point3dVector3d_1.Point3d.createZero(result);
    }
    /** Return the end point of of `this` curve. */
    endPoint(result) {
        const c = this._path.cyclicCurvePrimitive(-1);
        if (c)
            return c.endPoint(result);
        return Point3dVector3d_1.Point3d.createZero(result);
    }
    /** Add strokes to caller-supplied linestring */
    emitStrokes(dest, options) {
        for (const c of this._path.children) {
            c.emitStrokes(dest, options);
        }
    }
    /**
     * Ask the curve to announce points and simple subcurve fragments for stroking.
     * See IStrokeHandler for description of the sequence of the method calls.
     */
    emitStrokableParts(dest, options) {
        for (const c of this._path.children) {
            c.emitStrokableParts(dest, options);
        }
    }
    /**
     * Return the stroke count required for given options.
     * @param options StrokeOptions that determine count
     */
    computeStrokeCountForOptions(options) {
        let numStroke = 0;
        for (const c of this._path.children) {
            numStroke += c.computeStrokeCountForOptions(options);
        }
        return numStroke;
    }
    /**
     * Return an array containing only the curve primitives.
     * @param collectorArray array to receive primitives (pushed -- the array is not cleared)
     * @param smallestPossiblePrimitives if true, recurse on the children. If false, only push `this`.
     * @param explodeLinestrings (if smallestPossiblePrimitives is true) whether to push a [[LineSegment3d]] for each
     * segment of a [[LineString3d]] child. If false, push only the [[LineString3d]].
     */
    collectCurvePrimitivesGo(collectorArray, smallestPossiblePrimitives = false, explodeLineStrings = false) {
        if (smallestPossiblePrimitives) {
            for (const c of this._path.children) {
                c.collectCurvePrimitivesGo(collectorArray, smallestPossiblePrimitives, explodeLineStrings);
            }
        }
        else {
            collectorArray.push(this);
        }
    }
    /**
     * Construct StrokeCountMap for each child, accumulating data to stroke count map for this primitive.
     * @param options StrokeOptions that determine count
     * @param parentStrokeMap evolving parent map.
     */
    computeAndAttachRecursiveStrokeCounts(options, parentStrokeMap) {
        const myMap = StrokeCountMap_1.StrokeCountMap.createWithCurvePrimitiveAndOptionalParent(this, parentStrokeMap);
        for (const c of this._path.children) {
            c.computeAndAttachRecursiveStrokeCounts(options, myMap);
        }
        CurvePrimitive_1.CurvePrimitive.installStrokeCountMap(this, myMap, parentStrokeMap);
    }
    /**
     * Second step of double dispatch: call `this._path.dispatchToGeometryHandler (handler)`
     * * Note that this exposes the children individually to the handler.
     */
    dispatchToGeometryHandler(handler) {
        return handler.handleCurveChainWithDistanceIndex(this);
    }
    /** Extend `rangeToExtend` as needed to include these curves (optionally transformed) */
    extendRange(rangeToExtend, transform) {
        this._path.extendRange(rangeToExtend, transform);
    }
    /** Return a (high accuracy and positive) length of the curve between fractional positions */
    curveLengthBetweenFractions(fraction0, fraction1) {
        return Math.abs(fraction1 - fraction0) * this._totalLength;
    }
    /** Flatten CurveChainWithDistanceIndex children in the input chain.
     * @return cloned flattened CurveChain, or reference to the input chain if no nesting
    */
    static flattenNestedChains(chain) {
        if (-1 === chain.children.findIndex((child) => { return child instanceof CurveChainWithDistanceIndex; }))
            return chain;
        const flatChain = chain.clone();
        const flatChildren = flatChain.children.flatMap((child) => {
            if (child instanceof CurveChainWithDistanceIndex)
                return child.path.children;
            else
                return [child];
        });
        flatChain.children.splice(0, Infinity, ...flatChildren);
        return flatChain;
    }
    /**
     * Capture (not clone) a path into a new `CurveChainWithDistanceIndex`.
     * @param path chain to be CAPTURED (not cloned)
     * @param options how finely to stroke the path to create the distance index
     */
    static createCapture(path, options) {
        path = this.flattenNestedChains(path); // nested chains not allowed
        const fragments = DistanceIndexConstructionContext.createPathFragmentIndex(path, options);
        return new CurveChainWithDistanceIndex(path, fragments);
    }
    /**
     * Return the PathFragment object at the given `distance` along the chain.
     * @param distance distance along the chain.
     * @param allowExtrapolation if `true`, returns first fragment for negative distances and returns last fragment
     * for distances larger than curve length. If `false` returns `undefined` for those out of bound distances.
     */
    chainDistanceToFragment(distance, allowExtrapolation = false) {
        const i = this.chainDistanceToFragmentIndex(distance, allowExtrapolation);
        if (undefined !== i)
            return this._fragments[i];
        return undefined;
    }
    /**
     * Return the index of the PathFragment at the given `distance` along the chain.
     * @param distance distance along the chain.
     * @param allowExtrapolation if `true`, returns 0 for negative distances and returns last fragment index for
     * distances larger than curve length. If `false` returns `undefined` for those out of bound distances.
     */
    chainDistanceToFragmentIndex(distance, allowExtrapolation = false) {
        const numFragments = this._fragments.length;
        const fragments = this._fragments;
        if (numFragments > 0) {
            if (distance < 0.0)
                return allowExtrapolation ? 0 : undefined;
            if (distance > this._totalLength)
                return allowExtrapolation ? (numFragments - 1) : undefined;
            // linear search (opportunity for improvement)
            for (let i = 0; i < numFragments; i++) {
                if (fragments[i].containsChainDistance(distance))
                    return i;
            }
        }
        return undefined;
    }
    /**
     * Convert distance along the chain to fraction along the chain.
     * @param distance distance along the chain.
     */
    chainDistanceToChainFraction(distance) {
        return distance / this._totalLength;
    }
    /** Return the PathFragment object containing the point at the given `fraction` of the given child curve. */
    curveAndChildFractionToFragment(curve, fraction) {
        const numFragments = this._fragments.length;
        const fragments = this._fragments;
        if (numFragments > 0) {
            if (fraction < 0)
                return fragments[0];
            if (fraction > 1.0)
                return fragments[numFragments - 1];
            // linear search (opportunity for improvement)
            for (const fragment of fragments) {
                if (fragment.containsChildCurveAndChildFraction(curve, fraction))
                    return fragment;
            }
        }
        return undefined;
    }
    /** Returns the total length of `this` curve. */
    curveLength() {
        return this._totalLength;
    }
    /**
     * Returns the total length of the path.
     * * This is exact (and simple property lookup) because the true lengths were summed at construction time.
     */
    quickLength() {
        return this._totalLength;
    }
    /**
     * Return the point (x,y,z) on the curve at fractional position along the chain.
     * @param fraction fractional position along the curve.
     * @returns a point on the curve.
     */
    fractionToPoint(fraction, result) {
        const distanceAlongPath = fraction * this._totalLength;
        const fragment = this.chainDistanceToFragment(distanceAlongPath, true);
        if (fragment) {
            const childFraction = fragment.chainDistanceToAccurateChildFraction(distanceAlongPath, true);
            return fragment.childCurve.fractionToPoint(childFraction, result);
        }
        (0, core_bentley_1.assert)(false); // we never expect to get here
        // no fragment found. just return the first point on the curve.
        return this._fragments[0].childCurve.fractionToPoint(0.0, result);
    }
    /**
     * Return the point (x,y,z) and derivative on the curve at fractional position.
     * * Note that the derivative is "derivative of xyz with respect to fraction".
     * * The derivative shows the speed of the "fractional point" moving along the curve.
     * * The derivative is not generally a unit vector. Use `fractionToPointAndUnitTangent` for a unit vector.
     * @param fraction fractional position along the geometry.
     * @param result optional receiver for the result.
     * @returns a ray whose origin is the curve point and direction is the derivative with respect to the fraction.
     */
    fractionToPointAndDerivative(fraction, result) {
        const distanceAlongPath = fraction * this._totalLength;
        const fragment = this.chainDistanceToFragment(distanceAlongPath, true);
        const childFraction = fragment.chainDistanceToAccurateChildFraction(distanceAlongPath, true);
        result = fragment.childCurve.fractionToPointAndDerivative(childFraction, result);
        // Recall the standard arclength formula s(t) for the curve C = C(t), with derivative s'(t) = ||C'||.
        // Define fractional arclength for C by f = f(t) = s(t)/L, where L is the total length of C. Then f' = ||C'||/L.
        // Denote the inverse of f by t = t(f). Then C = C(t(f)) is a parameterization of C by its fractional arclength f.
        // Since the derivative of t is t'=1/f'=L/||C'||, the derivative we seek is d/df(C(t(f))) = C' t' = C' L/||C'||.
        // The fragment gives us C', so we're just a scale away.
        // Math details can be found at core/geometry/internaldocs/Curve.md
        const a = this._totalLength / result.direction.magnitude(); // L/||C'||
        result.direction.scaleInPlace(a);
        return result;
    }
    /**
     * Return the point (x,y,z) and normalized derivative on the curve at fractional position.
     * * Note that the derivative is "derivative of xyz with respect to fraction".
     * * The un-normalized derivative shows the speed of the "fractional point" moving along the curve.
     * * To find the un-normalized derivative, use `fractionToPointAndDerivative`.
     * @param fraction fractional position on the curve
     * @param result optional receiver for the result.
     * @returns a ray whose origin is the curve point and direction is the normalized derivative with respect to
     * the fraction.
     */
    fractionToPointAndUnitTangent(fraction, result) {
        const distanceAlongPath = fraction * this._totalLength;
        const fragment = this.chainDistanceToFragment(distanceAlongPath, true);
        const childFraction = fragment.chainDistanceToAccurateChildFraction(distanceAlongPath, true);
        result = fragment.childCurve.fractionToPointAndDerivative(childFraction, result);
        result.direction.normalizeInPlace();
        return result;
    }
    /**
     * Return a plane with
     * * origin at fractional position along the curve
     * * vectorU is the first derivative, i.e. tangent vector with length equal to the rate of change with respect to
     * the fraction.
     * * vectorV is the second derivative, i.e. derivative of vectorU which points in the direction of the curve's
     * derivative's change.
     */
    fractionToPointAnd2Derivatives(fraction, result) {
        const distanceAlongPath = fraction * this._totalLength;
        const fragment = this.chainDistanceToFragment(distanceAlongPath, true);
        const childFraction = fragment.chainDistanceToAccurateChildFraction(distanceAlongPath, true);
        result = fragment.childCurve.fractionToPointAnd2Derivatives(childFraction, result);
        if (!result)
            return undefined;
        // See fractionToPointAndDerivative, where we show d/df(C(t(f))) = L C'/||C'||.
        // Here we seek the 2nd derivative. We'll use the quotient rule, and the identities
        // d/dt||x(t)|| = x.x'/||x|| and ||x||^2 = x.x, where "." is the dot product.
        // d2/df2(C(t(f))) = L d/df(C'/||C'||) = L (||C'|| d/df(C') - C' d/df||C'||) / ||C'||^2
        //  = L (||C'|| C" L/||C'|| - C' C'.C"/||C'|| L/||C'||) / ||C'||^2
        //  = (L/||C'||)^2 (C" - C' C'.C"/C'.C' ), where C' and C" are given by the fragment.
        // The second derivative that fractionToPointAnd2Derivatives returns is C", so the second
        // derivative we seek is just few scales away.
        // Math details can be found at core/geometry/internaldocs/Curve.md
        const magU = result.vectorU.magnitude(); // ||C'||
        const dotUU = magU * magU; // ||C'||^2
        const dotUV = result.vectorU.dotProduct(result.vectorV); // C'.C"
        result.vectorV.addScaledInPlace(result.vectorU, -dotUV / dotUU); // add -(C'*C'.C")/(||C'||^2) to vectorV
        const scale = this._totalLength / magU; // L/||C'||
        result.vectorU.scaleInPlace(scale); // scale vectorU by L/||C'||
        result.vectorV.scaleInPlace(scale * scale); // scale vectorV by (L/||C'(t)||)^2
        return result;
    }
    /**
     * Attempt to transform in place.
     * * Warning: If any child transform fails, `this` object becomes invalid but that should never happen.
     * @param transform the transform to be applied.
     * @returns true if all of child transforms succeed and false otherwise.
     */
    tryTransformInPlace(transform) {
        let numFail = 0;
        for (const c of this._path.children) {
            if (!c.tryTransformInPlace(transform))
                numFail++;
        }
        return numFail === 0;
    }
    /** Reverse the curve's data so that its fractional stroking moves in the opposite direction. */
    reverseInPlace() {
        this._path.reverseChildrenInPlace();
        for (const fragment of this._fragments) {
            fragment.reverseFractionsAndDistances(this._totalLength);
        }
        this._fragments.reverse();
    }
    /**
     * Test for equality conditions.
     * * Mismatched total length is a quick exit condition.
     * * If total length matches, recurse to the path for matching primitives.
     */
    isAlmostEqual(other) {
        if (other instanceof CurveChainWithDistanceIndex) {
            return Geometry_1.Geometry.isSameCoordinate(this._totalLength, other._totalLength) && this._path.isAlmostEqual(other._path);
        }
        return false;
    }
    /**
     * (Attempt to) find a position on the curve at a signed distance from start fraction.
     * * See `CurvePrimitive.moveSignedDistanceFromFraction` for parameter details.
     * * The returned location directly identifies fractional position along the CurveChainWithDistanceIndex and
     * has pointer to an additional detail for the child curve.
     */
    moveSignedDistanceFromFraction(startFraction, signedDistance, allowExtension, result) {
        const distanceA = startFraction * this._totalLength;
        const distanceB = distanceA + signedDistance;
        const fragmentB = this.chainDistanceToFragment(distanceB, true);
        const childDetail = fragmentB.childCurve.moveSignedDistanceFromFraction(fragmentB.childFraction0, distanceB - fragmentB.chainDistance0, allowExtension, result?.childDetail); // local detail related to the child curve
        const endFraction = startFraction + (signedDistance / this._totalLength);
        const chainDetail = CurveLocationDetail_1.CurveLocationDetail.createConditionalMoveSignedDistance(allowExtension, this, startFraction, endFraction, signedDistance, result); // global detail related to the curve chain
        chainDetail.childDetail = childDetail;
        return chainDetail;
    }
    /**
     * Return an object summarizing closest point test counts.
     * The returned object has
     * * numCalls = number of times closestPoint was called.
     * * numCurvesTested = number of curves tested with full closestPoint.
     * * numAssigned = number of times a new minimum value was recorded.
     * * numCandidate = number of curves that would be tested in worst case.
     * @param clear if true, counts are cleared after the return object is formed.
     */
    static getClosestPointTestCounts(clear = true) {
        const a = {
            numCalls: this._numCalls,
            numTested: this._numTested,
            numAssigned: this._numAssigned,
            numCandidate: this._numCandidate,
        };
        if (clear) {
            this._numTested = this._numAssigned = this._numCandidate = 0;
        }
        return a;
    }
    /**
     * Search for the curve point that is closest to the spacePoint.
     * * The CurveChainWithDistanceIndex invokes the base class CurvePrimitive method, which (via a handler)
     * determines a CurveLocation detail among the children.
     * * The returned detail directly identifies fractional position along the CurveChainWithDistanceIndex and
     * has pointer to an additional detail for the child curve.
     * @param spacePoint point in space
     * @param extend true to extend the curve
     * @param result optional pre-allocated detail to populate and return.
     * @returns details of the closest point
     */
    closestPoint(spacePoint, extend, result) {
        let childDetail;
        let aMin = Number.MAX_VALUE;
        const numChildren = this.path.children.length;
        if (numChildren === 1) {
            childDetail = this.path.children[0].closestPoint(spacePoint, extend);
        }
        else {
            const sortedFragments = PathFragment.collectSortedQuickMinDistances(this._fragments, spacePoint);
            const extend0 = [
                CurveExtendMode_1.CurveExtendOptions.resolveVariantCurveExtendParameterToCurveExtendMode(extend, 0),
                CurveExtendMode_1.CurveExtendMode.None,
            ];
            const extend1 = [
                CurveExtendMode_1.CurveExtendMode.None,
                CurveExtendMode_1.CurveExtendOptions.resolveVariantCurveExtendParameterToCurveExtendMode(extend, 1),
            ];
            const fragment0 = this._fragments[0];
            const fragment1 = this._fragments[this._fragments.length - 1];
            CurveChainWithDistanceIndex._numCalls++;
            CurveChainWithDistanceIndex._numCandidate += sortedFragments.length;
            let detailA;
            for (const sortedFragment of sortedFragments) {
                if (sortedFragment.a > aMin)
                    // sortedFragments help early exit because it is likely that one of the first few fragments
                    // in sortedFragments is the fragment with minimum distance from space point to the curve.
                    break;
                CurveChainWithDistanceIndex._numTested++;
                const child = sortedFragment.childCurve;
                detailA = child.closestPoint(spacePoint, sortedFragment === fragment0 ? extend0 : sortedFragment === fragment1 ? extend1 : false, detailA);
                if (detailA && detailA.a < aMin) {
                    aMin = detailA.a;
                    childDetail = detailA.clone(childDetail);
                    CurveChainWithDistanceIndex._numAssigned++;
                }
            }
        }
        if (!childDetail)
            return undefined;
        return this.computeChainDetail(childDetail, result);
    }
    /**
     * Construct an offset of each child as viewed in the xy-plane (ignoring z).
     * * No attempt is made to join the offset children. Use RegionOps.constructCurveXYOffset to return a fully
     * joined offset.
     * @param offsetDistanceOrOptions offset distance (positive to left of the instance curve) or offset options object.
     */
    constructOffsetXY(offsetDistanceOrOptions) {
        const options = OffsetOptions_1.OffsetOptions.create(offsetDistanceOrOptions);
        const offsets = [];
        for (const prim of this.collectCurvePrimitives(undefined, true, true)) {
            const offset = prim.constructOffsetXY(options);
            if (offset !== undefined) {
                if (offset instanceof CurvePrimitive_1.CurvePrimitive)
                    offsets.push(offset);
                else if (Array.isArray(offset))
                    offset.forEach((cp) => offsets.push(cp));
            }
        }
        return offsets;
    }
    /**
     * Project instance geometry (via dispatch) onto the given ray, and return the extreme fractional parameters of
     * projection.
     * @param ray ray onto which the instance is projected. A `Vector3d` is treated as a `Ray3d` with zero origin.
     * @param lowHigh optional receiver for output.
     * @returns range of fractional projection parameters onto the ray, where 0.0 is start of the ray and 1.0 is the
     * end of the ray.
     */
    projectedParameterRange(ray, lowHigh) {
        return PlaneAltitudeRangeContext_1.PlaneAltitudeRangeContext.findExtremeFractionsAlongDirection(this, ray, lowHigh);
    }
    /**
     * Compute the global chain detail corresponding to a local child detail.
     * @param childDetail the local (fragment) detail, captured as-is.
     * @param result optional pre-allocated detail to populate and return.
     * @returns global (chain) detail with its `childDetail` field pointing to the input and its `a`
     * field copied from the input, but if a PathFragment for `childDetail` cannot be resolved, return undefined.
     */
    computeChainDetail(childDetail, result) {
        if (!childDetail.curve)
            return undefined;
        const fragment = this.curveAndChildFractionToFragment(childDetail.curve, childDetail.fraction);
        if (fragment) {
            const chainDistance = fragment.childFractionToChainDistance(childDetail.fraction);
            const chainFraction = this.chainDistanceToChainFraction(chainDistance);
            const chainDetail = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(this, chainFraction, childDetail.point, result);
            chainDetail.childDetail = childDetail;
            chainDetail.a = childDetail.a;
            return chainDetail;
        }
        return undefined;
    }
    /**
     * Given a parent chain, convert the corresponding child details in the specified pairs.
     * * Converted details refer to the chain's global parameterization instead of the child's.
     * * It is assumed that for all i >= index0, `pairs[i].detailA.curve` is a child of chainA (similarly for chainB).
     * @param pairs array to mutate
     * @param index0 convert details of pairs in the tail of the array, starting at index0
     * @param chainA convert each specified detailA to the global parameterization of chainA
     * @param chainB convert each specified detailB to the global parameterization of chainB
     * @param compressAdjacent whether to remove adjacent duplicate pairs after conversion
     * @return the converted array
     * @internal
     */
    static convertChildDetailToChainDetail(pairs, index0, chainA, chainB, compressAdjacent) {
        for (let i = index0; i < pairs.length; ++i) {
            const childDetailPair = pairs[i];
            if (chainA) {
                const chainDetail = chainA.computeChainDetail(childDetailPair.detailA);
                if (chainDetail)
                    childDetailPair.detailA = chainDetail;
            }
            if (chainB) {
                const chainDetail = chainB.computeChainDetail(childDetailPair.detailB);
                if (chainDetail)
                    childDetailPair.detailB = chainDetail;
            }
        }
        if (compressAdjacent)
            pairs = CurveLocationDetail_1.CurveLocationDetailPair.removeAdjacentDuplicates(pairs, index0);
        return pairs;
    }
}
exports.CurveChainWithDistanceIndex = CurveChainWithDistanceIndex;
//# sourceMappingURL=CurveChainWithDistanceIndex.js.map