import _ from "lodash";
import { Vector2, Vector3 } from "math.gl";
import { OrbitViewport, } from "@deck.gl/core";
import { getTrajectory } from "../utils/trajectory";
import { MergedTextLayer } from "./mergedTextLayer";
import { FixedSizeExtension } from "../../../extensions/fixed-size-extension";
/**
 * Enum representing the orientation of well labels.
 */
export var LabelOrientation;
(function (LabelOrientation) {
    /**
     * Horizontal orientation.
     */
    LabelOrientation["HORIZONTAL"] = "horizontal";
    /**
     * Tangent to well trajectory orientation.
     */
    LabelOrientation["TANGENT"] = "tangent";
})(LabelOrientation || (LabelOrientation = {}));
const DEFAULT_PROPS = {
    id: "well-label-layer",
    autoPosition: false,
    getPositionAlongPath: 0,
    getText: (d) => d.properties.name,
    getAlignmentBaseline: "bottom",
    getSize: 10,
    getColor: [0, 0, 0, 255],
    zIncreasingDownwards: true,
    orientation: LabelOrientation.HORIZONTAL,
    backgroundPadding: [3, 1, 3, 1],
    getBorderColor: [0, 0, 0, 255],
    getBorderWidth: 1,
    pickable: true,
    transitions: {
        // Animate label position transitions in order to help tracking when labels are moving
        getPosition: {
            duration: 100,
            type: "interpolation",
        },
        // Prevent changed label texts from appearing before the label background
        // has moved into place
        getColor: {
            duration: 100,
            type: "interpolation",
            enter: ([r, g, b]) => [r, g, b, 0],
            easing: (t) => _.floor(t),
        },
        // Prevent changed background border from appearing before the label text
        getBorderColor: {
            duration: 1,
            type: "interpolation",
            enter: ([r, g, b]) => [r, g, b, 0],
            easing: (t) => _.floor(t),
        },
        // Cosmetic effects for fading in the label background
        getBackgroundColor: {
            duration: 1,
            type: "spring",
            enter: ([r, g, b]) => [r, g, b, 0],
        },
    },
    // Ensure labels honor pixel size in screen space
    extensions: [new FixedSizeExtension()],
};
const getCumulativeDistance = (well_xyz) => {
    const cumulativeDistance = [0];
    for (let i = 1; i < well_xyz.length; i++) {
        const p1 = well_xyz[i - 1];
        const p2 = well_xyz[i];
        if (!p1 || !p2) {
            continue;
        }
        const v0 = new Vector3(p1);
        const v1 = new Vector3(p2);
        const distance = v0.distance(v1);
        cumulativeDistance.push(cumulativeDistance[i - 1] + distance);
    }
    return cumulativeDistance;
};
/**
 * The `WellLabelLayer` class extends the `MergedTextLayer` to provide functionality for rendering well
 * labels in a subsurface viewer. It includes methods for calculating label positions and angles based
 * on well trajectory data and the current viewport.
 *
 * @template WellLabelLayerData - The data type for the well label layer.
 * @template WellLabelLayerProps - The properties type for the well label layer.
 */
export class WellLabelLayer extends MergedTextLayer {
    getSubLayerProps(sublayerProps) {
        var _a, _b, _c, _d;
        const angleUpdateTriggers = (_b = (_a = sublayerProps === null || sublayerProps === void 0 ? void 0 : sublayerProps.updateTriggers) === null || _a === void 0 ? void 0 : _a["getAngle"]) !== null && _b !== void 0 ? _b : [];
        const positionUpdateTriggers = (_d = (_c = sublayerProps === null || sublayerProps === void 0 ? void 0 : sublayerProps.updateTriggers) === null || _c === void 0 ? void 0 : _c["getPosition"]) !== null && _d !== void 0 ? _d : [];
        const newProps = Object.assign(Object.assign({}, sublayerProps), { getPosition: (d) => {
                return this.getAnnotationPosition(d, this.props.getPositionAlongPath);
            }, getAngle: (d) => this.getLabelAngle(d), updateTriggers: Object.assign(Object.assign({}, sublayerProps === null || sublayerProps === void 0 ? void 0 : sublayerProps.updateTriggers), { getAngle: [
                    ...angleUpdateTriggers,
                    this.context.viewport.constructor === OrbitViewport
                        ? this.context.viewport
                            .cameraPosition
                        : null, // No need for angle update trigger for OrthographicViewport and change in camera.
                    this.props.orientation,
                    this.props.getPositionAlongPath,
                ], getPosition: [
                    ...positionUpdateTriggers,
                    this.context.viewport.cameraPosition,
                    this.props.getPositionAlongPath,
                ] }) });
        return super.getSubLayerProps(newProps);
    }
    shouldUpdateState(params) {
        if (params.changeFlags.viewportChanged &&
            (params.props.autoPosition ||
                "tangent" === params.props.orientation)) {
            return true;
        }
        return super.shouldUpdateState(params);
    }
    updateState(params) {
        if (params.props.autoPosition && params.changeFlags.viewportChanged) {
            // Trigger dataChanged if labels move due to auto-positioning
            params.changeFlags.dataChanged = "autoPosition";
        }
        super.updateState(params);
    }
    getLabelAngle(well) {
        if (this.props.orientation === "horizontal") {
            return 0;
        }
        const position = Number(this.props.getPositionAlongPath);
        _.clamp(position, 0, 1);
        const a = this.getVectorAlongTrajectory(position, well);
        return a[0];
    }
    /**
     * Get the position of the annotation
     * @param well_data Well trajectory
     * @param annotationPosition position of the annotation along the trajectory
     * @returns world position of the annotation
     */
    getAnnotationPosition(well_data, annotationPosition) {
        let fraction = Number(annotationPosition);
        if (typeof annotationPosition === "function") {
            fraction = annotationPosition(well_data);
        }
        fraction = _.clamp(fraction, 0, 1);
        // Return a position as a fraction down the trajectory
        return this.getVectorAlongTrajectory(fraction, well_data)[1];
    }
    /**
     * Get angle and position at a given fraction down the trajectory.
     * @param fraction Fraction down the trajectory [0, 1]
     * @param wellData Well trajectory
     */
    getVectorAlongTrajectory(fraction, wellData) {
        var _a;
        if (!wellData) {
            return [0, [0, 0, 0]];
        }
        const trajectory = getTrajectory(wellData, undefined);
        if (_.isUndefined(trajectory) || trajectory.length < 2) {
            return [0, [0, 0, 0]];
        }
        const { width, height } = this.context.viewport;
        const candidateFractions = [0.5, 0.25, 0.75, 0.125, 0.87, 0.37, 0.62];
        const cumulativeDistance = getCumulativeDistance(trajectory);
        const zSign = this.props.zIncreasingDownwards
            ? new Vector3(1, 1, -1)
            : new Vector3(1, 1, 1);
        while (candidateFractions.length != 0) {
            const targetDistance = cumulativeDistance[cumulativeDistance.length - 1] * fraction;
            // Find the index of the segment that contains the target distance
            let targetIndex = 0;
            for (let i = 0; i < cumulativeDistance.length - 1; i++) {
                if (cumulativeDistance[i + 1] < targetDistance) {
                    continue;
                }
                targetIndex = i;
                break;
            }
            const segmentBegin = new Vector3([...trajectory[targetIndex]]);
            const segmentEnd = new Vector3([...trajectory[targetIndex + 1]]);
            // Invert z if z is increasing downwards
            segmentBegin.multiply(zSign);
            segmentEnd.multiply(zSign);
            const distanceAlongTrajectory1 = cumulativeDistance[targetIndex];
            const distanceAlongTrajectory2 = cumulativeDistance[targetIndex + 1];
            const lineDistance = distanceAlongTrajectory2 - distanceAlongTrajectory1;
            const fractionOnLine = (targetDistance - distanceAlongTrajectory1) / lineDistance;
            // Linear interpolation between segmentBegin and segmentEnd
            const labelPosition = new Vector3(segmentBegin).lerp(segmentEnd, fractionOnLine);
            // If autoPosition is enabled, check if the label is outside the viewport
            // and if so, try the next candidate fraction
            if (this.props.autoPosition) {
                const ps = this.project(labelPosition);
                if (ps[0] < 0 || ps[0] > width || ps[1] < 0 || ps[1] > height) {
                    // If the label is outside view/camera, reposition it
                    fraction = (_a = candidateFractions.shift()) !== null && _a !== void 0 ? _a : 0;
                    continue;
                }
            }
            const p1 = this.project(segmentBegin);
            const p2 = this.project(segmentEnd);
            const v = new Vector2(p2[0] - p1[0], -(p2[1] - p1[1]));
            v.normalize();
            const rad = Math.atan2(v[1], v[0]);
            const deg = rad * (180 / Math.PI);
            let a = deg;
            if (deg > 90) {
                a = deg - 180;
            }
            else if (deg < -90) {
                a = deg + 180;
            }
            return [a, labelPosition.toArray()];
        }
        // Default to well head of no valid position is found in viewport
        return [0, trajectory[0]];
    }
    getPickingInfo({ info }) {
        var _a, _b, _c;
        const name = (_c = (_b = (_a = info.object) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : "";
        const pos = this.state.labelPositions.get(name);
        const names = this.state.clusters.get(pos);
        info.object = Object.assign(Object.assign({}, info.object), { wellLabels: names });
        return info;
    }
}
WellLabelLayer.defaultProps = DEFAULT_PROPS;
WellLabelLayer.layerName = "WellLabelLayer";
//# sourceMappingURL=wellLabelLayer.js.map