"""Bounded world-frame tracking and motion-state qualification.

The detector, semantic masks, LiDAR cloud, calibration and source result stay
immutable.  This stage associates accepted 3D measurements in ``k1-map`` and
adds a conservative ``static``/``dynamic``/``unknown`` state with explicit
uncertainty.  It is diagnostic only and has no navigation or command authority.
"""

from __future__ import annotations

import hashlib
import json
import math
import os
import secrets
import shutil
import statistics
import time
import zipfile
from collections import Counter, deque
from dataclasses import dataclass, field
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

import numpy as np

from k1link.artifacts import write_json_atomic
from k1link.sessions import SessionIntegrityError

from .integrated_perception import IntegratedPerceptionResult
from .jobs import CameraComputeJob

PROFILE_SCHEMA = "missioncore.e24-world-motion-profile/v1"
BENCHMARK_SCHEMA = "missioncore.e24-motion-benchmark/v1"
PIPELINE_ID = "world-frame-cv-tracker-motion-hysteresis/v1"


@dataclass(slots=True)
class _Track:
    track_id: int
    group: str
    label: str
    aliases: set[int]
    state: np.ndarray
    covariance: np.ndarray
    half_size: np.ndarray
    quaternion: np.ndarray
    created_seconds: float
    state_seconds: float
    last_observed_seconds: float
    hits: int
    misses: int
    score: float
    template: dict[str, Any]
    history: deque[tuple[float, np.ndarray]] = field(default_factory=deque)
    motion_state: str = "unknown"
    dynamic_evidence: int = 0
    static_evidence: int = 0
    motion_confidence: float = 0.0
    last_speed_mps: float | None = None


@dataclass(frozen=True, slots=True)
class WorldMotionBuild:
    result_root: Path
    profile_sha256: str
    benchmark_sha256: str
    report: dict[str, Any]


class WorldMotionTracker:
    """Streaming constant-velocity Kalman tracker in the host map frame."""

    def __init__(self, profile: dict[str, Any]) -> None:
        self.profile = profile
        self.tracks: dict[int, _Track] = {}
        self.next_track_id = int(profile["bounds"]["track_id_start"])
        self.peak_tracks = 0
        self.created_tracks = 0
        self.reassociated_source_ids = 0
        self.held_cuboids = 0
        self.published_motion_states: Counter[str] = Counter()

    def update(
        self,
        *,
        frame_index: int,
        session_seconds: float,
        objects: list[dict[str, Any]],
    ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
        """Return normalized fusion objects and conservative world objects."""

        self._predict_all(session_seconds)
        measurements = self._measurement_candidates(objects)
        assignments = self._associate(measurements, session_seconds)
        matched_tracks: set[int] = set()
        normalized_by_source: dict[int, dict[str, Any]] = {}
        for measurement_index, track_id in assignments.items():
            source = measurements[measurement_index]
            track = self.tracks[track_id]
            self._observe(track, source, session_seconds)
            matched_tracks.add(track_id)
            normalized_by_source[id(source)] = self._fusion_object(
                track,
                source,
                session_seconds,
                observed=True,
            )

        for index, source in enumerate(measurements):
            if index in assignments:
                continue
            track = self._create(source, session_seconds)
            matched_tracks.add(track.track_id)
            normalized_by_source[id(source)] = self._fusion_object(
                track,
                source,
                session_seconds,
                observed=True,
            )

        fusion_objects: list[dict[str, Any]] = []
        for source in objects:
            normalized = normalized_by_source.get(id(source))
            if normalized is None:
                normalized = json.loads(json.dumps(source))
                normalized["motion_state"] = "unknown"
                normalized["motion_status"] = "unavailable-no-accepted-3d-measurement"
            fusion_objects.append(normalized)

        hold_seconds = float(self.profile["tracking"]["hold_seconds"])
        minimum_hits = int(self.profile["tracking"]["minimum_confirmation_hits"])
        for track in sorted(self.tracks.values(), key=lambda item: item.track_id):
            if track.track_id in matched_tracks or track.hits < minimum_hits:
                continue
            age = session_seconds - track.last_observed_seconds
            if not 0.0 < age <= hold_seconds:
                continue
            held = self._fusion_object(
                track,
                track.template,
                session_seconds,
                observed=False,
            )
            fusion_objects.append(held)
            self.held_cuboids += 1

        world_objects = [
            self._world_object(item)
            for item in fusion_objects
            if str(item.get("cuboid_status", "")).startswith("accepted-")
        ]
        self.published_motion_states.update(str(item["motion_state"]) for item in world_objects)
        self._prune(session_seconds)
        self.peak_tracks = max(self.peak_tracks, len(self.tracks))
        return fusion_objects, world_objects

    def snapshot(self) -> dict[str, Any]:
        counts = {"static": 0, "dynamic": 0, "unknown": 0}
        for track in self.tracks.values():
            counts[track.motion_state] += 1
        return {
            "active_tracks": len(self.tracks),
            "peak_tracks": self.peak_tracks,
            "created_tracks": self.created_tracks,
            "reassociated_source_ids": self.reassociated_source_ids,
            "held_cuboids": self.held_cuboids,
            "active_motion_states": counts,
            "published_motion_states": dict(self.published_motion_states),
        }

    def _predict_all(self, now: float) -> None:
        acceleration_sigma = float(self.profile["filter"]["acceleration_sigma_mps2"])
        for track in self.tracks.values():
            dt = now - track.state_seconds
            if dt <= 0:
                continue
            transition = np.eye(6, dtype=np.float64)
            transition[:3, 3:] = np.eye(3, dtype=np.float64) * dt
            q_position = 0.25 * dt**4 * acceleration_sigma**2
            q_cross = 0.5 * dt**3 * acceleration_sigma**2
            q_velocity = dt**2 * acceleration_sigma**2
            process = np.block(
                [
                    [
                        np.eye(3, dtype=np.float64) * q_position,
                        np.eye(3, dtype=np.float64) * q_cross,
                    ],
                    [
                        np.eye(3, dtype=np.float64) * q_cross,
                        np.eye(3, dtype=np.float64) * q_velocity,
                    ],
                ]
            )
            track.state = transition @ track.state
            track.covariance = transition @ track.covariance @ transition.T + process
            track.state_seconds = now
            track.misses += 1

    def _associate(
        self,
        measurements: list[dict[str, Any]],
        now: float,
    ) -> dict[int, int]:
        candidates: list[tuple[float, int, int]] = []
        config = self.profile["association"]
        gate = float(config["maximum_center_distance_m"])
        alias_bonus = float(config["source_track_alias_bonus"])
        size_weight = float(config["size_distance_weight"])
        for measurement_index, source in enumerate(measurements):
            center = self._measurement_center(source)
            half_size = self._measurement_half_size(source)
            group = str(source.get("association_group", source.get("label", "object")))
            source_id = int(source["track_id"])
            for track in self.tracks.values():
                if not _compatible_group(group, track.group):
                    continue
                age = now - track.last_observed_seconds
                known_alias = source_id in track.aliases
                if not known_alias and age > float(config["maximum_reassociation_gap_seconds"]):
                    continue
                if known_alias:
                    adaptive_gate = gate + min(
                        float(config["maximum_speed_mps"]) * max(0.0, age),
                        float(config["maximum_gap_allowance_m"]),
                    )
                else:
                    gates = config["maximum_reassociation_distance_m"]
                    adaptive_gate = float(gates.get(group, gates["default"]))
                distance = float(np.linalg.norm(center - track.state[:3]))
                if distance > adaptive_gate:
                    continue
                size_distance = float(
                    np.linalg.norm(half_size - track.half_size)
                    / max(0.5, np.linalg.norm(track.half_size))
                )
                cost = distance + size_weight * size_distance
                if known_alias:
                    cost -= alias_bonus
                candidates.append((cost, measurement_index, track.track_id))
        assigned_measurements: set[int] = set()
        assigned_tracks: set[int] = set()
        result: dict[int, int] = {}
        for _, measurement_index, track_id in sorted(candidates):
            if measurement_index in assigned_measurements or track_id in assigned_tracks:
                continue
            assigned_measurements.add(measurement_index)
            assigned_tracks.add(track_id)
            result[measurement_index] = track_id
        return result

    def _create(self, source: dict[str, Any], now: float) -> _Track:
        center = self._measurement_center(source)
        initial_position_sigma = float(self.profile["filter"]["initial_position_sigma_m"])
        initial_velocity_sigma = float(self.profile["filter"]["initial_velocity_sigma_mps"])
        covariance = np.diag(
            [initial_position_sigma**2] * 3 + [initial_velocity_sigma**2] * 3
        ).astype(np.float64)
        track = _Track(
            track_id=self.next_track_id,
            group=str(source.get("association_group", source.get("label", "object"))),
            label=str(source.get("label", "object")),
            aliases={int(source["track_id"])},
            state=np.concatenate([center, np.zeros(3, dtype=np.float64)]),
            covariance=covariance,
            half_size=self._measurement_half_size(source),
            quaternion=self._measurement_quaternion(source),
            created_seconds=now,
            state_seconds=now,
            last_observed_seconds=now,
            hits=1,
            misses=0,
            score=float(source.get("score", 0.0)),
            template=json.loads(json.dumps(source)),
        )
        track.history.append((now, center.copy()))
        self.tracks[track.track_id] = track
        self.next_track_id += 1
        self.created_tracks += 1
        return track

    def _observe(self, track: _Track, source: dict[str, Any], now: float) -> None:
        measurement = self._measurement_center(source)
        support = max(1, int(source.get("clustered_points", 1)))
        config = self.profile["filter"]
        sigma_key = (
            "measurement_sigma_m"
            if self._measurement_quality(source) == "accepted"
            else "provisional_measurement_sigma_m"
        )
        sigma = float(config[sigma_key]) / math.sqrt(
            min(support, int(config["support_points_saturation"]))
        )
        sigma = max(float(config["minimum_measurement_sigma_m"]), sigma)
        measurement_covariance = np.eye(3, dtype=np.float64) * sigma**2
        observation = np.zeros((3, 6), dtype=np.float64)
        observation[:, :3] = np.eye(3, dtype=np.float64)
        innovation = measurement - observation @ track.state
        innovation_covariance = (
            observation @ track.covariance @ observation.T + measurement_covariance
        )
        gain = track.covariance @ observation.T @ np.linalg.inv(innovation_covariance)
        track.state = track.state + gain @ innovation
        identity = np.eye(6, dtype=np.float64)
        # Joseph form keeps the covariance positive under long recorded runs.
        residual = identity - gain @ observation
        track.covariance = (
            residual @ track.covariance @ residual.T + gain @ measurement_covariance @ gain.T
        )
        source_id = int(source["track_id"])
        if source_id not in track.aliases:
            self.reassociated_source_ids += 1
            track.aliases.add(source_id)
        shape_alpha = float(config["shape_alpha"])
        track.half_size = (
            1.0 - shape_alpha
        ) * track.half_size + shape_alpha * self._measurement_half_size(source)
        track.quaternion = self._measurement_quaternion(source)
        track.label = str(source.get("label", track.label))
        track.score = float(source.get("score", track.score))
        track.template = json.loads(json.dumps(source))
        track.last_observed_seconds = now
        track.hits += 1
        track.misses = 0
        track.history.append((now, track.state[:3].copy()))
        history_seconds = float(self.profile["motion"]["history_seconds"])
        while track.history and now - track.history[0][0] > history_seconds:
            track.history.popleft()
        self._classify(track)

    def _classify(self, track: _Track) -> None:
        config = self.profile["motion"]
        speed, direction_consistency = _robust_motion(track.history)
        track.last_speed_mps = speed
        if speed is None:
            track.motion_state = "unknown"
            track.motion_confidence = 0.0
            return
        history_duration = track.history[-1][0] - track.history[0][0]
        displacement = float(np.linalg.norm(track.history[-1][1] - track.history[0][1]))
        dynamic_thresholds = config["dynamic_enter_speed_mps"]
        dynamic_threshold = float(
            dynamic_thresholds.get(track.group, dynamic_thresholds["default"])
        )
        displacement_thresholds = config["dynamic_minimum_displacement_m"]
        displacement_threshold = float(
            displacement_thresholds.get(
                track.group,
                displacement_thresholds["default"],
            )
        )
        dynamic = (
            history_duration >= float(config["minimum_history_seconds"])
            and speed >= dynamic_threshold
            and displacement >= displacement_threshold
            and direction_consistency >= float(config["minimum_direction_consistency"])
        )
        static = (
            history_duration >= float(config["minimum_history_seconds"])
            and speed <= float(config["static_enter_speed_mps"])
            and displacement <= float(config["static_maximum_displacement_m"])
        )
        track.dynamic_evidence = track.dynamic_evidence + 1 if dynamic else 0
        track.static_evidence = track.static_evidence + 1 if static else 0
        dynamic_confirmations = config["dynamic_confirmation_frames"]
        dynamic_frames = int(
            dynamic_confirmations.get(
                track.group,
                dynamic_confirmations["default"],
            )
        )
        static_frames = int(config["static_confirmation_frames"])
        if track.dynamic_evidence >= dynamic_frames:
            track.motion_state = "dynamic"
        elif track.static_evidence >= static_frames:
            track.motion_state = "static"
        elif (
            track.motion_state == "dynamic" and speed < float(config["dynamic_exit_speed_mps"])
        ) or (track.motion_state == "static" and speed > float(config["static_exit_speed_mps"])):
            track.motion_state = "unknown"
        if track.motion_state == "dynamic":
            track.motion_confidence = min(
                1.0,
                speed / max(dynamic_threshold, 1e-6),
            )
        elif track.motion_state == "static":
            track.motion_confidence = max(
                0.0,
                1.0 - speed / max(float(config["static_exit_speed_mps"]), 1e-6),
            )
        else:
            track.motion_confidence = 0.0

    def _fusion_object(
        self,
        track: _Track,
        source: dict[str, Any],
        now: float,
        *,
        observed: bool,
    ) -> dict[str, Any]:
        normalized_value = json.loads(json.dumps(source))
        if not isinstance(normalized_value, dict):
            raise SessionIntegrityError("LAB E24 fusion object copy is invalid")
        normalized: dict[str, Any] = normalized_value
        normalized["source_track_id"] = int(source["track_id"])
        normalized["track_id"] = track.track_id
        quality = self._measurement_quality(source)
        publishable = quality == "accepted" or track.hits >= int(
            self.profile["tracking"]["provisional_confirmation_hits"]
        )
        if publishable:
            normalized["cuboid_center_map"] = track.state[:3].tolist()
            normalized["cuboid_half_size"] = track.half_size.tolist()
            normalized["cuboid_quaternion_xyzw"] = track.quaternion.tolist()
            normalized["cuboid_status"] = (
                (
                    "accepted-world-tracked-e24-v1"
                    if quality == "accepted"
                    else "accepted-world-track-provisional-e24-v1"
                )
                if observed
                else "accepted-world-track-hold-e24-v1"
            )
            normalized["geometry"] = (
                (
                    str(source.get("geometry", "accepted-3d-measurement"))
                    if quality == "accepted"
                    else "provisional-world-track-from-visible-lidar-support"
                )
                if observed
                else "predicted-world-frame-track-hold"
            )
        normalized["motion_state"] = track.motion_state
        normalized["motion_confidence"] = track.motion_confidence
        normalized["motion_status"] = (
            f"observed-world-frame-cv-{quality}"
            if observed and publishable
            else "tentative-world-frame-measurement"
            if observed
            else "predicted-world-frame-cv-hold"
        )
        normalized["velocity_map_mps"] = track.state[3:].tolist()
        normalized["speed_mps"] = track.last_speed_mps
        normalized["position_covariance_m2"] = np.diag(track.covariance[:3, :3]).tolist()
        normalized["velocity_covariance_m2ps2"] = np.diag(track.covariance[3:, 3:]).tolist()
        normalized["track_age_s"] = max(0.0, now - track.created_seconds)
        normalized["observation_age_ms"] = max(0.0, (now - track.last_observed_seconds) * 1000.0)
        normalized["track_hits"] = track.hits
        normalized["temporal_status"] = "e24-observed" if observed else "e24-held-prediction"
        return normalized

    def _measurement_candidates(
        self,
        objects: list[dict[str, Any]],
    ) -> list[dict[str, Any]]:
        result: list[dict[str, Any]] = []
        vehicle_aliases = {
            alias
            for track in self.tracks.values()
            if track.group == "vehicle"
            for alias in track.aliases
        }
        allowed_groups = set(self.profile["provisional"]["allowed_groups"])
        for source in objects:
            if self._measurement_quality(source) == "accepted":
                result.append(source)
                continue
            center = source.get("observed_cuboid_center_map")
            group = str(source.get("association_group", source.get("label", "object")))
            if (
                group not in allowed_groups
                or not _valid_vector(center, 3)
                or (group == "vehicle" and int(source["track_id"]) not in vehicle_aliases)
            ):
                continue
            result.append(source)
        return result

    @staticmethod
    def _measurement_quality(source: dict[str, Any]) -> str:
        return (
            "accepted"
            if str(source.get("cuboid_status", "")).startswith("accepted-")
            else "provisional"
        )

    @staticmethod
    def _measurement_center(source: dict[str, Any]) -> np.ndarray:
        key = (
            "cuboid_center_map"
            if WorldMotionTracker._measurement_quality(source) == "accepted"
            else "observed_cuboid_center_map"
        )
        return np.asarray(source[key], dtype=np.float64)

    def _measurement_half_size(self, source: dict[str, Any]) -> np.ndarray:
        candidate = source.get("cuboid_half_size")
        if _valid_vector(candidate, 3):
            return np.asarray(candidate, dtype=np.float64)
        group = str(source.get("association_group", source.get("label", "object")))
        priors = self.profile["provisional"]["nominal_half_size_m"]
        return np.asarray(priors.get(group, priors["default"]), dtype=np.float64)

    @staticmethod
    def _measurement_quaternion(source: dict[str, Any]) -> np.ndarray:
        for key in ("cuboid_quaternion_xyzw", "observed_cuboid_quaternion_xyzw"):
            candidate = source.get(key)
            if _valid_vector(candidate, 4):
                return np.asarray(candidate, dtype=np.float64)
        return np.asarray([0.0, 0.0, 0.0, 1.0], dtype=np.float64)

    @staticmethod
    def _world_object(item: dict[str, Any]) -> dict[str, Any]:
        half_size = np.asarray(item["cuboid_half_size"], dtype=np.float64)
        return {
            "track_id": int(item["track_id"]),
            "source_track_id": int(item["source_track_id"]),
            "class": str(item.get("association_group", item.get("label", "object"))),
            "detector_label": str(item.get("label", "object")),
            "confidence": float(item.get("score", 0.0)),
            "position_map_m": list(item["cuboid_center_map"]),
            "orientation_map_xyzw": list(item["cuboid_quaternion_xyzw"]),
            "size_m": (2.0 * half_size).tolist(),
            "range_m": item.get("distance_smoothed_m"),
            "velocity_map_mps": list(item["velocity_map_mps"]),
            "speed_mps": item.get("speed_mps"),
            "velocity_status": item["motion_status"],
            "motion_state": item["motion_state"],
            "motion_confidence": item["motion_confidence"],
            "position_covariance_m2": list(item["position_covariance_m2"]),
            "velocity_covariance_m2ps2": list(item["velocity_covariance_m2ps2"]),
            "track_age_s": item["track_age_s"],
            "observation_age_ms": item["observation_age_ms"],
            "track_hits": item["track_hits"],
            "support_points": int(item.get("clustered_points", 0)),
            "geometry": item["geometry"],
            "temporal_status": item["temporal_status"],
        }

    def _prune(self, now: float) -> None:
        maximum_idle = float(self.profile["bounds"]["maximum_track_idle_seconds"])
        stale = [
            track_id
            for track_id, track in self.tracks.items()
            if now - track.last_observed_seconds > maximum_idle
        ]
        for track_id in stale:
            self.tracks.pop(track_id, None)
        maximum = int(self.profile["bounds"]["maximum_tracks"])
        if len(self.tracks) <= maximum:
            return
        ordered = sorted(
            self.tracks.values(),
            key=lambda track: (track.last_observed_seconds, track.hits),
        )
        for track in ordered[: len(self.tracks) - maximum]:
            self.tracks.pop(track.track_id, None)


def read_world_motion_profile(path: Path) -> tuple[dict[str, Any], str]:
    resolved = path.expanduser().resolve(strict=True)
    profile = _read_object(resolved)
    sections = [
        profile.get("source"),
        profile.get("association"),
        profile.get("filter"),
        profile.get("provisional"),
        profile.get("tracking"),
        profile.get("motion"),
        profile.get("bounds"),
        profile.get("acceptance"),
    ]
    if (
        profile.get("schema_version") != PROFILE_SCHEMA
        or profile.get("mode") != "recorded-streaming-qualification"
        or not all(isinstance(section, dict) for section in sections)
        or profile["source"].get("coordinate_frame") != "k1-map"
        or profile.get("authority")
        != {"commands_enabled": False, "navigation_or_safety_accepted": False}
        or not 1 <= int(profile["bounds"].get("maximum_tracks", 0)) <= 512
        or int(profile["tracking"].get("minimum_confirmation_hits", 0)) < 2
        or not isinstance(profile["motion"].get("dynamic_enter_speed_mps"), dict)
        or float(profile["motion"]["static_enter_speed_mps"])
        >= float(profile["motion"]["dynamic_enter_speed_mps"].get("default", -1))
    ):
        raise SessionIntegrityError("LAB E24 world-motion profile is invalid")
    return profile, _sha256(resolved)


def read_motion_benchmark(path: Path) -> tuple[dict[str, Any], str]:
    resolved = path.expanduser().resolve(strict=True)
    benchmark = _read_object(resolved)
    events = benchmark.get("events")
    if (
        benchmark.get("schema_version") != BENCHMARK_SCHEMA
        or not isinstance(events, list)
        or not events
        or benchmark.get("source_session_id") != "20260720T065719Z_viewer_live"
    ):
        raise SessionIntegrityError("LAB E24 motion benchmark is invalid")
    identifiers: set[str] = set()
    for event in events:
        if (
            not isinstance(event, dict)
            or not isinstance(event.get("id"), str)
            or event["id"] in identifiers
            or event.get("expected_motion") not in {"static", "dynamic"}
            or event.get("class_group")
            not in {"person", "vehicle", "bicycle", "vulnerable_road_user"}
            or not isinstance(event.get("window_seconds"), list)
            or len(event["window_seconds"]) != 2
            or float(event["window_seconds"][0]) >= float(event["window_seconds"][1])
        ):
            raise SessionIntegrityError("LAB E24 benchmark event is invalid")
        identifiers.add(event["id"])
    return benchmark, _sha256(resolved)


def evaluate_motion_benchmark(
    world_rows: list[dict[str, Any]],
    benchmark: dict[str, Any],
) -> dict[str, Any]:
    outcomes: list[dict[str, Any]] = []
    for event in benchmark["events"]:
        start, end = (float(value) for value in event["window_seconds"])
        observations: list[tuple[float, dict[str, Any]]] = []
        for row in world_rows:
            timestamp = float(row["session_seconds"])
            if not start <= timestamp <= end:
                continue
            for item in row["objects"]:
                class_matches = (
                    item.get("class") in {"person", "bicycle", "motorcycle"}
                    if event["class_group"] == "vulnerable_road_user"
                    else item.get("class") == event["class_group"]
                )
                if (
                    class_matches
                    and item.get("motion_state") == event["expected_motion"]
                    and float(item.get("observation_age_ms", 0.0))
                    <= float(event.get("maximum_observation_age_ms", 500.0))
                ):
                    observations.append((timestamp, item))
        by_track: dict[int, list[float]] = {}
        for timestamp, item in observations:
            by_track.setdefault(int(item["track_id"]), []).append(timestamp)
        best_track: int | None = None
        best_hits = 0
        best_span = 0.0
        for track_id, timestamps in by_track.items():
            span = max(timestamps) - min(timestamps)
            if (len(timestamps), span) > (best_hits, best_span):
                best_track = track_id
                best_hits = len(timestamps)
                best_span = span
        minimum_hits = int(event.get("minimum_hits", 3))
        minimum_span = float(event.get("minimum_span_seconds", 0.2))
        passed = best_hits >= minimum_hits and best_span >= minimum_span
        outcomes.append(
            {
                "id": event["id"],
                "expected_motion": event["expected_motion"],
                "class_group": event["class_group"],
                "window_seconds": [start, end],
                "passed": passed,
                "best_track_id": best_track,
                "best_hits": best_hits,
                "best_span_seconds": best_span,
            }
        )
    return {
        "schema_version": "missioncore.e24-motion-benchmark-result/v1",
        "passed": all(item["passed"] for item in outcomes),
        "passed_events": sum(item["passed"] for item in outcomes),
        "total_events": len(outcomes),
        "events": outcomes,
    }


def build_world_motion_result(
    *,
    source: IntegratedPerceptionResult,
    lab_job: CameraComputeJob,
    lab_pack: Path,
    results_root: Path,
    profile_path: Path,
    benchmark_path: Path,
) -> WorldMotionBuild:
    """Build an immutable E24 projection from one accepted integrated result."""

    if not source.accepted:
        raise SessionIntegrityError("LAB E24 source result is not accepted")
    profile, profile_sha256 = read_world_motion_profile(profile_path)
    benchmark, benchmark_sha256 = read_motion_benchmark(benchmark_path)
    if source.job.session_id != benchmark["source_session_id"]:
        raise SessionIntegrityError("LAB E24 benchmark source session differs")
    source_document = _read_object(source.result_root / "result.json")
    source_report = _read_object(source.report_path)
    source_fusion = _read_jsonl(source.fusion_path)
    source_world = _read_jsonl(source.world_path)
    if len(source_fusion) != source.frame_count or len(source_world) != source.frame_count:
        raise SessionIntegrityError("LAB E24 source timelines are incomplete")

    tracker = WorldMotionTracker(profile)
    fusion_rows: list[dict[str, Any]] = []
    world_rows: list[dict[str, Any]] = []
    frame_ms: list[float] = []
    for fusion, world in zip(source_fusion, source_world, strict=True):
        started = time.perf_counter()
        fusion_objects, world_objects = tracker.update(
            frame_index=int(fusion["frame_index"]),
            session_seconds=float(fusion["session_seconds"]),
            objects=list(fusion["objects"]),
        )
        normalized_fusion = json.loads(json.dumps(fusion))
        normalized_fusion["objects"] = fusion_objects
        normalized_fusion["world_motion"] = {
            "schema_version": "missioncore.e24-world-motion-frame/v1",
            "coordinate_frame": "k1-map",
            "lookahead_frames": 0,
        }
        normalized_world = json.loads(json.dumps(world))
        normalized_world["objects"] = world_objects
        normalized_world["object_count"] = len(world_objects)
        delivery = normalized_world.setdefault("delivery", {})
        delivery["world_motion"] = "e24-world-frame-cv-hysteresis"
        elapsed = (time.perf_counter() - started) * 1000.0
        delivery["world_motion_processing_ms"] = elapsed
        fusion_rows.append(normalized_fusion)
        world_rows.append(normalized_world)
        frame_ms.append(elapsed)

    benchmark_result = evaluate_motion_benchmark(world_rows, benchmark)
    runtime = _percentiles(frame_ms)
    checks = {
        "source_payloads_immutable": True,
        "diagnostic_authority_only": True,
        "single_pass_no_lookahead": True,
        "track_state_bound": tracker.peak_tracks
        <= int(profile["acceptance"]["maximum_tracks_observed"]),
        "processing_p95_ms": runtime["p95"]
        <= float(profile["acceptance"]["maximum_processing_p95_ms"]),
    }
    accepted = all(checks.values())
    source_identity = source_document["identity"]
    configuration = {
        "pipeline": PIPELINE_ID,
        "profile_sha256": profile_sha256,
        "profile": profile,
        "implementation_sha256": _sha256(Path(__file__).resolve()),
        "benchmark_sha256": benchmark_sha256,
        "benchmark": benchmark,
        "source_result_id": source.result_id,
        "source_result_sha256": _sha256(source.result_root / "result.json"),
        "source_session_id": source.job.session_id,
        "processing": "single-pass-bounded-state-no-lookahead",
    }
    identity = {
        "schema_version": "missioncore.e10-integrated-perception-identity/v1",
        "job_id": lab_job.job_id,
        "input_sha256": lab_job.input_sha256,
        "session_id": lab_job.session_id,
        "source_id": lab_job.source_id,
        "lidar_pack_id": lab_pack.name,
        "selection": json.loads(json.dumps(source_identity["selection"])),
        "configuration": configuration,
        "models": source_identity["models"],
    }
    identity_sha256 = hashlib.sha256(_canonical(identity)).hexdigest()
    result_id = f"e10-integrated-perception-{identity_sha256}"
    destination = results_root / result_id
    created_at = datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    report = {
        "schema_version": "missioncore.e10-integrated-perception-report/v1",
        "result_id": result_id,
        "created_at_utc": created_at,
        "state": "accepted" if accepted else "rejected",
        "ground_truth": False,
        "identity": identity,
        "acceptance": {
            "accepted": accepted,
            "navigation_or_safety_accepted": False,
            "checks": checks,
        },
        "metrics": {
            "runtime": {
                "world_motion_frame_processing_ms": runtime,
                **tracker.snapshot(),
            },
            "benchmark": benchmark_result,
            "source_realtime_envelope": source_report.get("metrics", {}),
        },
        "runtime": {
            "executor": "local-deterministic-postprocessor",
            "numpy": np.__version__,
            "streaming_equivalent": True,
            "lookahead_frames": 0,
        },
        "limitations": [
            "Recorded diagnostic qualification only; no navigation or command authority.",
            "Motion state is tracking-by-detection evidence, not labeled ground truth.",
            "User-supplied event timestamps are approximate operator annotations.",
            "Unknown is intentional when motion evidence is insufficient or contradictory.",
            "Raw LiDAR, ground returns, camera media, calibration and model outputs are unchanged.",
        ],
    }
    if destination.exists():
        existing = _read_object(destination / "result.json")
        if existing.get("identity") != identity:
            raise SessionIntegrityError("LAB E24 result id collides")
        return WorldMotionBuild(
            destination,
            profile_sha256,
            benchmark_sha256,
            _read_object(destination / "run-report.json"),
        )

    results_root.mkdir(mode=0o700, parents=True, exist_ok=True)
    staging = results_root / f".{result_id}.{secrets.token_hex(12)}.tmp"
    staging.mkdir(mode=0o700)
    try:
        semantic_path = staging / "semantic-frames.jsonl"
        fusion_path = staging / "fusion-frames.jsonl"
        world_path = staging / "world-state.jsonl"
        arrays_path = staging / "transient-perception.npz"
        gpu_path = staging / "gpu-telemetry.jsonl"
        report_path = staging / "run-report.json"
        os.link(source.semantic_path, semantic_path, follow_symlinks=False)
        _write_jsonl(fusion_path, fusion_rows)
        _write_jsonl(world_path, world_rows)
        box_offsets, centers, half_sizes, quaternions, colors = _box_arrays(fusion_rows)
        _rewrite_npz_boxes(
            source.arrays_path,
            arrays_path,
            {
                "box_offsets": box_offsets,
                "box_centers": centers,
                "box_half_sizes": half_sizes,
                "box_quaternions": quaternions,
                "box_colors": colors,
            },
        )
        os.link(
            source.result_root / "gpu-telemetry.jsonl",
            gpu_path,
            follow_symlinks=False,
        )
        write_json_atomic(report_path, report)
        artifacts = [
            _artifact("e10-semantic-frames", semantic_path, "missioncore.e10-semantic-frame/v1"),
            _artifact("e10-fusion-frames", fusion_path, "missioncore.e10-fusion-frame/v1"),
            _artifact("e10-world-state", world_path, "missioncore.live-perception-world-state/v1"),
            _artifact("e10-transient-perception", arrays_path, None),
            _artifact("worker-gpu-telemetry", gpu_path, None),
            _artifact(
                "e10-run-report",
                report_path,
                "missioncore.e10-integrated-perception-report/v1",
            ),
        ]
        write_json_atomic(
            staging / "result.json",
            {
                "schema_version": "missioncore.e10-integrated-perception-result/v1",
                "result_id": result_id,
                "identity_sha256": identity_sha256,
                "identity": identity,
                "created_at_utc": created_at,
                "ground_truth": False,
                "publication_scope": "recorded-integrated-realtime-qualification-only",
                "acceptance_state": "accepted" if accepted else "rejected",
                "frames_processed": source.frame_count,
                "artifacts": artifacts,
            },
        )
        os.replace(staging, destination)
    finally:
        if staging.exists():
            shutil.rmtree(staging)
    return WorldMotionBuild(destination, profile_sha256, benchmark_sha256, report)


def _robust_motion(
    history: deque[tuple[float, np.ndarray]],
) -> tuple[float | None, float]:
    if len(history) < 3:
        return None, 0.0
    velocities: list[np.ndarray] = []
    values = list(history)
    for start in range(len(values) - 1):
        for end in range(start + 1, len(values)):
            dt = values[end][0] - values[start][0]
            if dt < 0.35:
                continue
            velocities.append((values[end][1] - values[start][1]) / dt)
    if not velocities:
        return None, 0.0
    planar = np.stack(velocities)[:, :2]
    velocity = np.median(planar, axis=0)
    norms = np.linalg.norm(planar, axis=1)
    directional = planar[norms > 1e-6] / norms[norms > 1e-6, None]
    consistency = (
        0.0 if len(directional) == 0 else float(np.linalg.norm(np.mean(directional, axis=0)))
    )
    return float(np.linalg.norm(velocity)), consistency


def _compatible_group(left: str, right: str) -> bool:
    if left == right:
        return True
    vulnerable = {"person", "bicycle", "motorcycle"}
    return left in vulnerable and right in vulnerable


def _valid_vector(value: Any, length: int) -> bool:
    if not isinstance(value, list) or len(value) != length:
        return False
    return all(
        isinstance(item, int | float) and not isinstance(item, bool) and math.isfinite(float(item))
        for item in value
    )


def _box_arrays(
    fusion_rows: list[dict[str, Any]],
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    offsets = [0]
    centers: list[list[float]] = []
    sizes: list[list[float]] = []
    quaternions: list[list[float]] = []
    colors: list[list[int]] = []
    for row in fusion_rows:
        for item in row["objects"]:
            if not str(item.get("cuboid_status", "")).startswith("accepted-"):
                continue
            centers.append(item["cuboid_center_map"])
            sizes.append(item["cuboid_half_size"])
            quaternions.append(item["cuboid_quaternion_xyzw"])
            colors.append(_cuboid_color(item))
        offsets.append(len(centers))
    return (
        np.asarray(offsets, dtype=np.int64),
        np.asarray(centers, dtype=np.float32).reshape((-1, 3)),
        np.asarray(sizes, dtype=np.float32).reshape((-1, 3)),
        np.asarray(quaternions, dtype=np.float32).reshape((-1, 4)),
        np.asarray(colors, dtype=np.uint8).reshape((-1, 4)),
    )


def _rewrite_npz_boxes(
    source: Path,
    destination: Path,
    replacements: dict[str, np.ndarray],
) -> None:
    replacement_names = {f"{name}.npy" for name in replacements}
    with (
        zipfile.ZipFile(source, "r") as reader,
        zipfile.ZipFile(
            destination,
            "x",
            compression=zipfile.ZIP_DEFLATED,
            compresslevel=6,
            allowZip64=True,
        ) as writer,
    ):
        for info in reader.infolist():
            if info.filename in replacement_names:
                continue
            with (
                reader.open(info, "r") as source_stream,
                writer.open(
                    info.filename,
                    "w",
                    force_zip64=True,
                ) as target_stream,
            ):
                shutil.copyfileobj(source_stream, target_stream, length=1024 * 1024)
        for name, array in replacements.items():
            with writer.open(f"{name}.npy", "w", force_zip64=True) as stream:
                np.lib.format.write_array(stream, array, allow_pickle=False)
    os.chmod(destination, 0o600)


def _cuboid_color(item: dict[str, Any]) -> list[int]:
    state = item.get("motion_state")
    if state == "dynamic":
        return [245, 81, 87, 112]
    if state == "static":
        return [75, 190, 128, 88]
    return [238, 180, 55, 88]


def _percentiles(values: list[float]) -> dict[str, float]:
    if not values:
        return {"mean": 0.0, "p50": 0.0, "p95": 0.0, "maximum": 0.0}
    ordered = np.asarray(values, dtype=np.float64)
    return {
        "mean": float(statistics.fmean(values)),
        "p50": float(np.percentile(ordered, 50)),
        "p95": float(np.percentile(ordered, 95)),
        "maximum": float(np.max(ordered)),
    }


def _read_jsonl(path: Path) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    with path.open(encoding="utf-8") as stream:
        for line in stream:
            value = json.loads(line)
            if not isinstance(value, dict):
                raise SessionIntegrityError("LAB E24 JSONL row is not an object")
            rows.append(value)
    return rows


def _write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None:
    with path.open("x", encoding="utf-8") as stream:
        for row in rows:
            stream.write(
                json.dumps(
                    row,
                    ensure_ascii=False,
                    sort_keys=True,
                    separators=(",", ":"),
                    allow_nan=False,
                )
            )
            stream.write("\n")
        stream.flush()
        os.fsync(stream.fileno())
    os.chmod(path, 0o600)


def _read_object(path: Path) -> dict[str, Any]:
    value = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(value, dict):
        raise SessionIntegrityError("LAB E24 JSON document is not an object")
    return value


def _artifact(kind: str, path: Path, schema: str | None) -> dict[str, Any]:
    return {
        "kind": kind,
        "path": path.name,
        "schema_version": schema,
        "byte_length": path.stat().st_size,
        "sha256": _sha256(path),
    }


def _sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        while block := stream.read(1024 * 1024):
            digest.update(block)
    return digest.hexdigest()


def _canonical(value: Any) -> bytes:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        allow_nan=False,
    ).encode()
