#if targetEnvironment(simulator)
import Foundation
import RideFormCore

/// Explicit UI-test source through the real capture adapter's validation boundary.
@MainActor
final class SimulatorMotionFixture {
    private let capture: MotionCapture
    private let stage: () -> (CalibrationController.Stage, CalibrationController.SetupMethod)
    private let mode: CaptureMode
    private var timer: Timer?
    private var callback: ((CapturedRecord) -> Void)?
    private static var generation = 0
    private let base: Quaternion
    init(capture: MotionCapture, mode: CaptureMode, stage: @escaping () -> (CalibrationController.Stage, CalibrationController.SetupMethod)) {
        self.capture = capture; self.stage = stage; self.mode = mode
        Self.generation += 1
        // Alternate distinct references without eventually reaching a 180° yaw, where
        // the deliberate gravity-based direction check cannot distinguish q from q⁻¹.
        base = OrientationMath.quaternion(rotationVector: Vector3(z: Self.generation.isMultiple(of: 2) ? 0.8 : 0.4))
    }
    func start() {
        capture.makeSimulatorFixtureCallback { [weak self] callback in
            DispatchQueue.main.async {
                guard let self else { return }
                self.callback = callback
                self.timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { [weak self] _ in
                    Task { @MainActor in self?.tick() }
                }
            }
        }
    }
    func stop() { timer?.invalidate(); timer = nil; callback = nil }
    private func tick() {
        let now = capture.clock.now()
        let rotation: Vector3
        let (current, method) = stage()
        switch current {
        case .left: rotation = method == .standardMount ? Vector3(z: -0.3) : Vector3(x: -0.3)
        case .forward, .awaitingConvention: rotation = method == .standardMount ? Vector3(x: 0.3) : Vector3(y: 0.3)
        case .ready: rotation = Vector3(x: sin(now) * 0.2)
        default: rotation = Vector3()
        }
        let q = OrientationMath.multiply(base, OrientationMath.quaternion(rotationVector: rotation))
        let x = OrientationMath.rotate(q, Vector3(x: 1)), y = OrientationMath.rotate(q, Vector3(y: 1)), z = OrientationMath.rotate(q, Vector3(z: 1))
        if mode != .rawOnly { callback?(.motion(MotionSample(sensorTimestamp: now, receiptUptime: now,
            attitudeQuaternion: q,
            attitudeRotationMatrix: RotationMatrix(m11: x.x, m12: y.x, m13: z.x, m21: x.y, m22: y.y, m23: z.y, m31: x.z, m32: y.z, m33: z.z),
            gravity: OrientationMath.rotate(OrientationMath.inverse(q), Vector3(z: -1))))) }
        if mode != .fusedOnly {
            callback?(.accelerometer(RawAccelerometerSample(sensorTimestamp: capture.clock.now(), receiptUptime: capture.clock.now(), acceleration: OrientationMath.rotate(OrientationMath.inverse(q), Vector3(z: -1)))))
            callback?(.gyroscope(RawGyroscopeSample(sensorTimestamp: capture.clock.now(), receiptUptime: capture.clock.now())))
        }
    }
}
#endif
