import { Picking } from "./Picking.js";
import { ControlVector3 } from "./NativeControlTypes.js";
export function createPickingAnnotationRoot(name, annotations) {
    return {
        type: "PickingAnnotationRoot",
        name,
        position: new ControlVector3(),
        children: annotations.map(createPickingAnnotationObject)
    };
}
export function createPickingAnnotationObject(annotation) {
    const radius = positiveFinite(annotation.radius, "annotation radius");
    const metadata = {
        id: annotation.id,
        label: annotation.label,
        kind: annotation.kind,
        group: annotation.group,
        selectable: annotation.selectable,
        highlightable: annotation.highlightable,
        pickRadius: radius,
        priority: annotation.priority,
        routeId: annotation.routeId,
        source: annotation.source,
        targetId: annotation.targetId,
        payload: annotation.payload
    };
    return {
        type: "PickingAnnotation",
        name: annotation.label,
        position: toVector3(annotation.position),
        scale: new ControlVector3(radius * 2, radius * 2, radius * 2),
        visible: annotation.selectable === false ? false : true,
        pickRadius: radius,
        pickPriority: annotation.priority,
        picking: metadata,
        userData: {
            a3dPicking: metadata,
            a3dPickingAnnotation: annotation
        }
    };
}
export function pickAnnotation(root, origin = new ControlVector3(), direction = new ControlVector3(0, 0, -1), options = {}) {
    const report = new Picking().report(root, origin, direction, options);
    const hits = options.hitPolicy === "priority"
        ? [...report.hits].sort(compareAnnotationHitsByPriority)
        : report.hits;
    const hit = hits[0] ?? null;
    return {
        ...report,
        hit,
        hits,
        annotation: hit ? annotationFromPickHit(hit) : null,
        annotations: hits.map((candidate) => annotationFromPickHit(candidate)).filter((annotation) => Boolean(annotation))
    };
}
export function annotationFromPickHit(hit) {
    const value = hit?.object.userData?.a3dPickingAnnotation;
    return isPickingAnnotation(value) ? value : null;
}
export function createImportedGlbHotspotAnnotations(assetId, hotspots, routeId) {
    return hotspots.map((hotspot, index) => {
        const targetId = hotspot.focusId ?? hotspot.nodeName ?? hotspot.id ?? `hotspot-${index}`;
        return {
            id: hotspot.id ?? `${assetId}:${targetId}`,
            label: hotspot.label ?? readableLabel(targetId),
            kind: "hotspot",
            position: hotspot.position,
            radius: hotspot.radius ?? 0.18,
            priority: hotspot.priority ?? 100,
            group: assetId,
            routeId,
            source: "imported-gltf",
            selectable: hotspot.selectable,
            highlightable: hotspot.highlightable ?? true,
            targetId,
            payload: hotspot.payload
        };
    });
}
export function createDistrictPickingAnnotations(districts) {
    const annotations = [];
    for (const district of districts) {
        annotations.push({
            id: district.id,
            label: district.label ?? readableLabel(district.id),
            kind: "district",
            position: district.center,
            radius: district.radius,
            priority: district.priority ?? 10,
            group: "district",
            routeId: district.routeId,
            source: "procedural",
            highlightable: true,
            payload: district.payload
        });
        for (const building of district.buildings ?? []) {
            annotations.push({
                id: building.id,
                label: building.label ?? readableLabel(building.id),
                kind: "building",
                position: building.position,
                radius: building.radius,
                priority: building.priority ?? 40,
                group: district.id,
                routeId: district.routeId,
                source: "procedural",
                highlightable: true,
                payload: building.payload
            });
        }
    }
    return annotations;
}
export function createRobotPickingAnnotations(robots) {
    return createEntityPickingAnnotations(robots.map((robot) => ({ ...robot, kind: "robot" })), {
        defaultGroup: "robot",
        defaultPriority: 70
    });
}
export function createEntityPickingAnnotations(entities, options = {}) {
    return entities.map((entity) => ({
        id: entity.id,
        label: entity.label ?? readableLabel(entity.id),
        kind: entity.kind ?? "entity",
        position: entity.position,
        radius: entity.radius,
        priority: entity.priority ?? options.defaultPriority ?? 50,
        group: entity.group ?? options.defaultGroup,
        routeId: entity.routeId,
        source: "procedural",
        highlightable: true,
        targetId: entity.id,
        payload: entity.payload
    }));
}
export function pickScreenSpaceAnnotation(pointer, annotations, options = {}) {
    validateScreenPoint(pointer, "pointer");
    const tolerance = options.tolerance ?? 0;
    if (!Number.isFinite(tolerance) || tolerance < 0)
        throw new RangeError("Screen picking tolerance must be finite and non-negative.");
    const hits = [];
    let skippedDisabled = 0;
    let skippedOutOfRadius = 0;
    for (const annotation of annotations) {
        if (annotation.selectable === false) {
            skippedDisabled += 1;
            continue;
        }
        validateScreenPoint(annotation, annotation.id);
        const radius = positiveFinite(annotation.radius, `${annotation.id} screen radius`) + tolerance;
        const distance = Math.hypot(pointer.x - annotation.x, pointer.y - annotation.y);
        if (distance > radius) {
            skippedOutOfRadius += 1;
            continue;
        }
        hits.push({ annotation, distance });
    }
    hits.sort(options.hitPolicy === "priority" ? compareScreenHitsByPriority : compareScreenHitsByDistance);
    const hit = hits[0] ?? null;
    return {
        hit,
        hits,
        diagnostics: {
            testedAnnotations: annotations.length,
            candidateAnnotations: annotations.length - skippedDisabled,
            hitCount: hits.length,
            skippedDisabled,
            skippedOutOfRadius,
            nearestHitLabel: hit?.annotation.label ?? null
        }
    };
}
function toVector3(value) {
    const tuple = value;
    const vector = value;
    const x = Array.isArray(value) ? tuple[0] : vector.x;
    const y = Array.isArray(value) ? tuple[1] : vector.y;
    const z = Array.isArray(value) ? tuple[2] : vector.z;
    if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
        throw new RangeError("Picking annotation position must contain three finite values.");
    }
    return new ControlVector3(x, y, z);
}
function positiveFinite(value, label) {
    if (!Number.isFinite(value) || value <= 0)
        throw new RangeError(`Picking ${label} must be finite and positive.`);
    return value;
}
function readableLabel(value) {
    return value.replace(/[_:-]+/g, " ").replace(/\s+/g, " ").trim() || "Picking target";
}
function compareAnnotationHitsByPriority(left, right) {
    const priorityDelta = (right.metadata?.priority ?? 0) - (left.metadata?.priority ?? 0);
    if (Math.abs(priorityDelta) > 1e-8)
        return priorityDelta;
    return left.distance - right.distance;
}
function isPickingAnnotation(value) {
    return Boolean(value)
        && typeof value === "object"
        && typeof value.id === "string"
        && typeof value.label === "string";
}
function validateScreenPoint(point, label) {
    if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {
        throw new RangeError(`Screen picking ${label} must contain finite x/y values.`);
    }
}
function compareScreenHitsByDistance(left, right) {
    const distanceDelta = left.distance - right.distance;
    if (Math.abs(distanceDelta) > 1e-8)
        return distanceDelta;
    return (right.annotation.priority ?? 0) - (left.annotation.priority ?? 0);
}
function compareScreenHitsByPriority(left, right) {
    const priorityDelta = (right.annotation.priority ?? 0) - (left.annotation.priority ?? 0);
    if (Math.abs(priorityDelta) > 1e-8)
        return priorityDelta;
    return left.distance - right.distance;
}
//# sourceMappingURL=PickingAnnotations.js.map