using System;
using System.Collections.Generic;
using System.Threading;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using Aidlab.BLE;
using UnityEngine;
using UnityEngine.Events;
using Stopwatch = System.Diagnostics.Stopwatch;

namespace Aidlab
{
    public class AidlabSDK : MonoBehaviour
    {
        private static bool initAttempted;

        public event Action Connected;
        public event Action Disconnected;

        public static void init()
        {
            init(null);
        }

	        public static void init(string deviceName)
	        {
            if (FindObjectOfType<AidlabSDK>() != null)
                return;

            if (initAttempted)
                return;

            initAttempted = true;

            var sdkObject = new GameObject("AidlabSDK");
            DontDestroyOnLoad(sdkObject);
            var instance = sdkObject.AddComponent<AidlabSDK>();
            if (!string.IsNullOrEmpty(deviceName))
            {
                instance.deviceNameToConnect = deviceName;
            }
	        }

	        [SerializeField] private string deviceNameToConnect = "Aidlab";
	        [SerializeField] private Signal[] liveSignalsToCollect = { Signal.Ecg, Signal.Temperature, Signal.HeartRate, Signal.Rr };
	        [SerializeField] private Signal[] syncSignalsToStore = new Signal[0];

        private readonly MainThreadWorker mainThreadWorker = new MainThreadWorker();
        public static readonly AidlabDelegate aidlabDelegate = new AidlabDelegate();
        public static readonly AidlabSyncDelegate aidlabSyncDelegate = new AidlabSyncDelegate();

        private IBleConnector bleConnector;
        public string firmwareRevisionStr = "";
        public string hardwareRevisionStr = "";

        private IntPtr sdk = IntPtr.Zero;
        private GCHandle contextHandle;

        private readonly object txLock = new object();
        private readonly Queue<byte[]> pendingFrames = new Queue<byte[]>();
        private volatile bool canSendFrame = true;

        private readonly object earlyRxLock = new object();
        private readonly Queue<byte[]> earlyRxChunks = new Queue<byte[]>();
        private int earlyRxBytesBuffered;
        private const int EarlyRxMaxBytes = 256 * 1024;

        private float lastDataReceivedTime = -1.0f;
        private bool receivedData = false;

        [Header("Diagnostics")]
        [SerializeField] private bool enablePacketTrace = false;

        private long debugRxPackets;
        private long debugRxBytes;
        private static long debugSignalUpdates;
        private long debugLastRxAtMs = -1;
        private long debugHrCallbacks;
        private long debugRrCallbacks;
        private long debugWearStateCallbacks;
        private long debugLastHrAtMs = -1;
        private long debugLastRrAtMs = -1;
        private long debugLastWearStateAtMs = -1;
        private int debugLastRxSize;
        private long debugCollectSentAtMs = -1;
        private long debugLastNoSignalLogAtMs = -1;

        public bool IsSdkInitialized => sdk != IntPtr.Zero;
	        public float SecondsSinceLastPacket => lastDataReceivedTime < 0.0f ? -1.0f : (Time.time - lastDataReceivedTime);
	        public string TargetDeviceName => deviceNameToConnect ?? "";

	        public long DebugHrCallbacks => Interlocked.Read(ref debugHrCallbacks);
	        public long DebugRrCallbacks => Interlocked.Read(ref debugRrCallbacks);
	        public long DebugWearStateCallbacks => Interlocked.Read(ref debugWearStateCallbacks);
	        public long DebugLastHrAtMs => Interlocked.Read(ref debugLastHrAtMs);
	        public long DebugLastRrAtMs => Interlocked.Read(ref debugLastRrAtMs);
	        public long DebugLastWearStateAtMs => Interlocked.Read(ref debugLastWearStateAtMs);

	        public Signal[] LiveSignalsToCollect => liveSignalsToCollect;
	        public Signal[] SyncSignalsToStore => syncSignalsToStore;

	        public void SetCollectSignals(Signal[] liveSignals, Signal[] syncSignals)
	        {
	            liveSignalsToCollect = liveSignals ?? Array.Empty<Signal>();
	            syncSignalsToStore = syncSignals ?? Array.Empty<Signal>();

	            if (sdk != IntPtr.Zero && !string.IsNullOrEmpty(firmwareRevisionStr))
	            {
	                StartCollectAll();
	            }
	        }

        public void SetTargetDeviceName(string value)
        {
            string next = value ?? "";
            if (next == deviceNameToConnect)
                return;

            if (enablePacketTrace)
                Debug.Log($"AidlabSDK: switching target device name '{deviceNameToConnect}' -> '{next}'");
            deviceNameToConnect = next;

            bleConnector?.Disconnect();
            bleConnector = null;

            if (sdk != IntPtr.Zero)
            {
                DestroySdk();
            }

            firmwareRevisionStr = "";
            hardwareRevisionStr = "";
            lastDataReceivedTime = -1.0f;
            receivedData = false;
            debugRxPackets = 0;
            debugRxBytes = 0;
            Interlocked.Exchange(ref debugSignalUpdates, 0);
            debugCollectSentAtMs = -1;
            debugLastNoSignalLogAtMs = -1;
            lock (earlyRxLock)
            {
                earlyRxChunks.Clear();
                earlyRxBytesBuffered = 0;
            }
        }

        public long DebugRxPackets => Interlocked.Read(ref debugRxPackets);
        public long DebugRxBytes => Interlocked.Read(ref debugRxBytes);
        public long DebugSignalUpdates => Interlocked.Read(ref debugSignalUpdates);
        public long DebugLastRxAtMs => Interlocked.Read(ref debugLastRxAtMs);
        public int DebugLastRxSize => Volatile.Read(ref debugLastRxSize);

        private static readonly double StopwatchTicksToMs = 1000.0 / Stopwatch.Frequency;

        public static long DebugNowMs()
        {
            // Unity can target older .NET profiles where Environment.TickCount64 is unavailable.
            // Stopwatch timestamp is monotonic and perfect for relative timing.
            return (long)(Stopwatch.GetTimestamp() * StopwatchTicksToMs);
        }

        public string TransportStatus
        {
            get
            {
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
                if (bleConnector is BLEConnector win)
                    return win.CurrentStatus.ToString();
                return "Unknown";
#elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
                if (bleConnector is MacBLEConnector mac)
                    return mac.IsConnected ? "Connected" : "Scanning";
                return "Unknown";
#else
                return "Unsupported";
#endif
            }
        }

        public string TransportLastError
        {
            get
            {
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
                if (bleConnector is BLEConnector win)
                    return win.LastError;
                return "";
#else
                return "";
#endif
            }
        }

        public List<BLEApi.DeviceUpdate> GetKnownDevicesSnapshot()
        {
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
            if (bleConnector is BLEConnector win)
                return win.GetKnownDevicesSnapshot();
#endif
            return new List<BLEApi.DeviceUpdate>();
        }

        private static readonly AidlabAPI.callbackSampleTime EcgCallback = OnEcg;
        private static readonly AidlabAPI.callbackSampleTime RespirationCallback = OnRespiration;
        private static readonly AidlabAPI.callbackSampleTime TemperatureCallback = OnTemperature;
        private static readonly AidlabAPI.callbackAccelerometer AccelerometerCallback = OnAccelerometer;
        private static readonly AidlabAPI.callbackGyroscope GyroscopeCallback = OnGyroscope;
        private static readonly AidlabAPI.callbackMagnetometer MagnetometerCallback = OnMagnetometer;
        private static readonly AidlabAPI.callbackBatteryLevel BatteryCallback = OnBattery;
        private static readonly AidlabAPI.callbackActivity ActivityCallback = OnActivity;
        private static readonly AidlabAPI.callbackSteps StepsCallback = OnSteps;
        private static readonly AidlabAPI.callbackOrientation OrientationCallback = OnOrientation;
        private static readonly AidlabAPI.callbackQuaternion QuaternionCallback = OnQuaternion;
        private static readonly AidlabAPI.callbackRespirationRate RespirationRateCallback = OnRespirationRate;
        private static readonly AidlabAPI.callbackWearState WearStateCallback = OnWearState;
        private static readonly AidlabAPI.callbackHeartRate HeartRateCallback = OnHeartRate;
        private static readonly AidlabAPI.callbackRr RrCallback = OnRr;
        private static readonly AidlabAPI.callbackSoundVolume SoundVolumeCallback = OnSoundVolume;
        private static readonly AidlabAPI.callbackExercise ExerciseCallback = OnExercise;
        private static readonly AidlabAPI.callbackUserEvent UserEventCallback = OnUserEvent;
        private static readonly AidlabAPI.callbackPressure PressureCallback = OnPressure;
        private static readonly AidlabAPI.callbackWearState PressureWearStateCallback = OnPressureWearState;
        private static readonly AidlabAPI.callbackBodyPosition BodyPositionCallback = OnBodyPosition;
        private static readonly AidlabAPI.callbackSignalQuality SignalQualityCallback = OnSignalQuality;
        private static readonly AidlabAPI.callbackEda EdaCallback = OnEda;
        private static readonly AidlabAPI.callbackGps GpsCallback = OnGps;

        private static readonly AidlabAPI.callbackSyncState SyncStateCallback = OnSyncState;
        private static readonly AidlabAPI.callbackUnsynchronizedSize UnsynchronizedSizeCallback = OnUnsynchronizedSize;
        private static readonly AidlabAPI.callbackSampleTime PastEcgCallback = OnPastEcg;
        private static readonly AidlabAPI.callbackSampleTime PastRespirationCallback = OnPastRespiration;
        private static readonly AidlabAPI.callbackSampleTime PastTemperatureCallback = OnPastTemperature;
        private static readonly AidlabAPI.callbackHeartRate PastHeartRateCallback = OnPastHeartRate;
        private static readonly AidlabAPI.callbackRr PastRrCallback = OnPastRr;
        private static readonly AidlabAPI.callbackActivity PastActivityCallback = OnPastActivity;
        private static readonly AidlabAPI.callbackRespirationRate PastRespirationRateCallback = OnPastRespirationRate;
        private static readonly AidlabAPI.callbackSteps PastStepsCallback = OnPastSteps;
        private static readonly AidlabAPI.callbackUserEvent PastUserEventCallback = OnPastUserEvent;
        private static readonly AidlabAPI.callbackSoundVolume PastSoundVolumeCallback = OnPastSoundVolume;
        private static readonly AidlabAPI.callbackPressure PastPressureCallback = OnPastPressure;
        private static readonly AidlabAPI.callbackAccelerometer PastAccelerometerCallback = OnPastAccelerometer;
        private static readonly AidlabAPI.callbackGyroscope PastGyroscopeCallback = OnPastGyroscope;
        private static readonly AidlabAPI.callbackQuaternion PastQuaternionCallback = OnPastQuaternion;
        private static readonly AidlabAPI.callbackOrientation PastOrientationCallback = OnPastOrientation;
        private static readonly AidlabAPI.callbackMagnetometer PastMagnetometerCallback = OnPastMagnetometer;
        private static readonly AidlabAPI.callbackBodyPosition PastBodyPositionCallback = OnPastBodyPosition;
        private static readonly AidlabAPI.callbackSignalQuality PastSignalQualityCallback = OnPastSignalQuality;
        private static readonly AidlabAPI.callbackEda PastEdaCallback = OnPastEda;
        private static readonly AidlabAPI.callbackGps PastGpsCallback = OnPastGps;

        private static readonly AidlabAPI.callbackBleSend BleSendCallback = OnBleSend;
        private static readonly AidlabAPI.callbackBleReady BleReadyCallback = OnBleReady;

        private static readonly AidlabAPI.callbackPayload PayloadCallback = OnPayload;
        private static readonly AidlabAPI.callbackLogMessage LogCallback = OnLogMessage;

        private void Awake()
        {
            lastDataReceivedTime = -1.0f;
            receivedData = false;
            debugRxPackets = 0;
            debugRxBytes = 0;
            Interlocked.Exchange(ref debugSignalUpdates, 0);
            Interlocked.Exchange(ref debugHrCallbacks, 0);
            Interlocked.Exchange(ref debugRrCallbacks, 0);
            Interlocked.Exchange(ref debugWearStateCallbacks, 0);
            Interlocked.Exchange(ref debugLastHrAtMs, -1);
            Interlocked.Exchange(ref debugLastRrAtMs, -1);
            Interlocked.Exchange(ref debugLastWearStateAtMs, -1);
            Volatile.Write(ref debugLastRxSize, 0);
            debugCollectSentAtMs = -1;
            debugLastNoSignalLogAtMs = -1;
            lock (earlyRxLock)
            {
                earlyRxChunks.Clear();
                earlyRxBytesBuffered = 0;
            }
            bleConnector = null;
        }

        private void EnsureConnector()
        {
            if (bleConnector != null)
                return;

#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
            bleConnector = new BLEConnector(this, deviceNameToConnect);
#elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
            bleConnector = new MacBLEConnector(this, deviceNameToConnect);
#elif UNITY_IOS
            bleConnector = new IosBLEConnector(this, deviceNameToConnect);
#else
            bleConnector = null;
#endif
        }

        private void Update()
        {
            EnsureConnector();
            TestConnection();
            bleConnector?.ConnectionProcess();
            mainThreadWorker.Update();
            FlushPendingFrames();
            DebugMaybeLogNoSignals();
        }

	        private void OnDisable()
	        {
	            ShutdownTransport();
	        }

	        public void ShutdownTransport()
	        {
	            var connector = bleConnector;
	            bleConnector = null;
	            connector?.Disconnect();
	            DestroySdk();
	        }

        private void TestConnection()
        {
            if (receivedData)
            {
                lastDataReceivedTime = Time.time;
                receivedData = false;
            }

            if (lastDataReceivedTime > 0.0f && Time.time - 8.0f > lastDataReceivedTime)
            {
                bleConnector?.Disconnect();
            }
        }

        public void OnAidlabConnected()
        {
            if (sdk != IntPtr.Zero)
                return;

            debugRxPackets = 0;
            debugRxBytes = 0;
            Interlocked.Exchange(ref debugSignalUpdates, 0);
            Interlocked.Exchange(ref debugHrCallbacks, 0);
            Interlocked.Exchange(ref debugRrCallbacks, 0);
            Interlocked.Exchange(ref debugWearStateCallbacks, 0);
            Interlocked.Exchange(ref debugLastHrAtMs, -1);
            Interlocked.Exchange(ref debugLastRrAtMs, -1);
            Interlocked.Exchange(ref debugLastWearStateAtMs, -1);
            debugCollectSentAtMs = -1;
            debugLastNoSignalLogAtMs = -1;

            string normalizedFirmware = NormalizeFirmwareRevision(firmwareRevisionStr ?? "");
            if (!string.Equals(normalizedFirmware, firmwareRevisionStr ?? "", StringComparison.Ordinal))
            {
                if (enablePacketTrace)
                    Debug.Log($"AidlabSDK: normalized firmware '{firmwareRevisionStr}' -> '{normalizedFirmware}'");
                firmwareRevisionStr = normalizedFirmware;
            }

            byte[] firmware = Encoding.UTF8.GetBytes(firmwareRevisionStr ?? "");
            if (firmware.Length == 0)
            {
                Debug.LogError("AidlabSDK: firmwareRevisionStr is empty");
                return;
            }

            try
            {
                sdk = AidlabAPI.AidlabSDK_create(firmware, firmware.Length);
            }
            catch (Exception ex)
            {
                Debug.LogError($"AidlabSDK: AidlabSDK_create threw: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}");
                return;
            }
            if (sdk == IntPtr.Zero)
            {
                Debug.LogError("AidlabSDK: AidlabSDK_create returned null");
                return;
            }

            contextHandle = GCHandle.Alloc(this);
            IntPtr contextPtr = GCHandle.ToIntPtr(contextHandle);
            AidlabAPI.AidlabSDK_set_context(contextPtr, sdk);

            AidlabAPI.AidlabSDK_set_log_callback(LogCallback, contextPtr, sdk);
            AidlabAPI.AidlabSDK_set_payload_callback(PayloadCallback, sdk);

            AidlabAPI.AidlabSDK_set_ble_send_callback(BleSendCallback, sdk);
            AidlabAPI.AidlabSDK_set_ble_ready_callback(BleReadyCallback, sdk);

            FlushEarlyRxChunks();

            AidlabAPI.AidlabSDK_init_callbacks(
                EcgCallback,
                RespirationCallback,
                TemperatureCallback,
                AccelerometerCallback,
                GyroscopeCallback,
                MagnetometerCallback,
                BatteryCallback,
                ActivityCallback,
                StepsCallback,
                OrientationCallback,
                QuaternionCallback,
                RespirationRateCallback,
                WearStateCallback,
                HeartRateCallback,
                RrCallback,
                SoundVolumeCallback,
                ExerciseCallback,
                UserEventCallback,
                PressureCallback,
                PressureWearStateCallback,
                BodyPositionCallback,
                SignalQualityCallback,
                sdk);

            AidlabAPI.AidlabSDK_init_synchronization_callbacks(
                SyncStateCallback,
                UnsynchronizedSizeCallback,
                PastEcgCallback,
                PastRespirationCallback,
                PastTemperatureCallback,
                PastHeartRateCallback,
                PastRrCallback,
                PastActivityCallback,
                PastRespirationRateCallback,
                PastStepsCallback,
                PastUserEventCallback,
                PastSoundVolumeCallback,
                PastPressureCallback,
                PastAccelerometerCallback,
                PastGyroscopeCallback,
                PastQuaternionCallback,
                PastOrientationCallback,
                PastMagnetometerCallback,
                PastBodyPositionCallback,
                PastSignalQualityCallback,
                sdk);

            AidlabAPI.AidlabSDK_set_eda_callback(EdaCallback, sdk);
            AidlabAPI.AidlabSDK_set_gps_callback(GpsCallback, sdk);
            AidlabAPI.AidlabSDK_set_past_eda_callback(PastEdaCallback, sdk);
            AidlabAPI.AidlabSDK_set_past_gps_callback(PastGpsCallback, sdk);

            canSendFrame = true;
            StartCollectAll();
            EnqueueMainThreadEvent(Connected);
        }

        private void FlushEarlyRxChunks()
        {
            Queue<byte[]> chunksToProcess = null;
            lock (earlyRxLock)
            {
                if (earlyRxChunks.Count == 0)
                    return;

                chunksToProcess = new Queue<byte[]>(earlyRxChunks);
                earlyRxChunks.Clear();
                earlyRxBytesBuffered = 0;
            }

            int count = chunksToProcess.Count;
            if (enablePacketTrace)
                Debug.Log($"AidlabSDK: flushing {count} early RX chunks");
            while (chunksToProcess.Count > 0)
            {
                byte[] chunk = chunksToProcess.Dequeue();
                if (chunk == null || chunk.Length == 0)
                    continue;
                AidlabAPI.AidlabSDK_process_ble_chunk(chunk, chunk.Length, sdk);
            }
        }

        private static string NormalizeFirmwareRevision(string value)
        {
            if (string.IsNullOrEmpty(value))
                return "";

            string trimmed = value.Trim();
            var match = Regex.Match(trimmed, @"(\d+)\.(\d+)\.(\d+)");
            if (match.Success)
            {
                return $"{match.Groups[1].Value}.{match.Groups[2].Value}.{match.Groups[3].Value}";
            }
            return trimmed;
        }

        public void StartCollectAll()
        {
            debugCollectSentAtMs = DebugNowMs();
            string firmware = firmwareRevisionStr ?? "";
            int major = ParseMajorFirmware(firmware);

            if (major >= 4)
            {
                StartCollectV4Flags();
                return;
            }

            if (CompareFirmware(firmware, "3.7.80") >= 0)
            {
                StartCollectV3Flags();
                return;
            }

            if (CompareFirmware(firmware, "3.6.0") >= 0)
            {
                StartCollectV3Binary();
                return;
            }

            SendCommand("collect");
        }

	        private void StartCollectV3Flags()
	        {
	            ushort liveFlags = (ushort)(BuildFlags16(liveSignalsToCollect) & 0xFFFF);
	            ushort syncFlags = (ushort)(BuildFlags16(syncSignalsToStore) & 0xFFFF);

            string liveHex = liveFlags.ToString("X4");
            string syncHex = syncFlags.ToString("X4");
            if (enablePacketTrace)
                Debug.Log($"AidlabSDK: collect flags {liveHex} {syncHex}");
            SendCommand($"collect flags {liveHex} {syncHex}");
        }

	        private void StartCollectV3Binary()
	        {
	            uint liveFlags = BuildFlags16(liveSignalsToCollect);
	            uint syncFlags = BuildFlags16(syncSignalsToStore);

            byte[] prefix = Encoding.UTF8.GetBytes("collect on ");
            byte[] payload = new byte[prefix.Length + 8];
            Buffer.BlockCopy(prefix, 0, payload, 0, prefix.Length);

            int offset = prefix.Length;
            payload[offset++] = (byte)((liveFlags >> 24) & 0xFF);
            payload[offset++] = (byte)((liveFlags >> 16) & 0xFF);
            payload[offset++] = (byte)((liveFlags >> 8) & 0xFF);
            payload[offset++] = (byte)(liveFlags & 0xFF);
            payload[offset++] = (byte)((syncFlags >> 24) & 0xFF);
            payload[offset++] = (byte)((syncFlags >> 16) & 0xFF);
            payload[offset++] = (byte)((syncFlags >> 8) & 0xFF);
            payload[offset] = (byte)(syncFlags & 0xFF);

            if (enablePacketTrace)
                Debug.Log($"AidlabSDK: collect on (binary), liveFlags=0x{liveFlags:X8} syncFlags=0x{syncFlags:X8}");
            AidlabAPI.AidlabSDK_send(payload, payload.Length, 8 /* collect */, sdk);
        }

		        private void StartCollectV4Flags()
		        {
		            uint liveFlags = BuildFlags32(liveSignalsToCollect);
		            uint syncFlags = BuildFlags32(syncSignalsToStore);

            string liveHex = liveFlags.ToString("X8");
            string syncHex = syncFlags.ToString("X8");
            if (enablePacketTrace)
                Debug.Log($"AidlabSDK: collect flags {liveHex} {syncHex} (v4)");
            SendCommand($"collect flags {liveHex} {syncHex}");
        }

	        private static uint BuildFlags16(Signal[] signals)
	        {
	            // Firmware 3.x uses a 16-bit flags map (0..15).
	            if (signals == null || signals.Length == 0)
	                return 0;

	            uint flags = 0;
	            foreach (Signal signal in signals)
	            {
	                int bit = (int)signal;
	                if (bit < 0 || bit > 15)
	                    continue;
	                flags |= (1u << bit);
	            }
	            return flags;
	        }

	        private static uint BuildFlags32(Signal[] signals)
	        {
	            // Firmware 4.x uses a 32-bit flags map and supports newer signals (EDA/GPS).
	            if (signals == null || signals.Length == 0)
	                return 0;

	            uint flags = 0;
	            foreach (Signal signal in signals)
	            {
	                int bit = (int)signal;
	                if (bit < 0 || bit > 31)
	                    continue;
	                flags |= (1u << bit);
	            }
	            return flags;
	        }

        private static int CompareFirmware(string firmware, string reference)
        {
            int[] a = ParseVersionParts(firmware);
            int[] b = ParseVersionParts(reference);

            for (int i = 0; i < 3; i++)
            {
                if (a[i] != b[i])
                    return a[i].CompareTo(b[i]);
            }
            return 0;
        }

        private static int[] ParseVersionParts(string value)
        {
            int[] parts = { 0, 0, 0 };
            if (string.IsNullOrEmpty(value))
                return parts;

            string[] raw = value.Trim().Split('.');
            for (int i = 0; i < 3 && i < raw.Length; i++)
            {
                int.TryParse(raw[i], out parts[i]);
            }
            return parts;
        }

        private static int ParseMajorFirmware(string firmware)
        {
            if (string.IsNullOrEmpty(firmware))
                return 0;

            int dot = firmware.IndexOf('.');
            string majorStr = dot >= 0 ? firmware.Substring(0, dot) : firmware;
            if (int.TryParse(majorStr, out int major))
                return major;
            return 0;
        }

        public void OnAidlabDataReceived(BLEApi.BLEData bleData)
        {
            receivedData = true;

            if (sdk == IntPtr.Zero)
                return;

            if (!AidlabCharacteristicsUUID.CmdUUID.Equals(bleData.serviceUuid, bleData.characteristicUuid))
                return;

            AidlabAPI.AidlabSDK_process_ble_chunk(bleData.buf, bleData.size, sdk);
        }

        public void OnAidlabDisconnected()
        {
            lastDataReceivedTime = -1.0f;
            receivedData = false;
            var connector = bleConnector;
            bleConnector = null;
            connector?.Disconnect();
            DestroySdk();
            firmwareRevisionStr = "";
            hardwareRevisionStr = "";
            EnqueueMainThreadEvent(Disconnected);
        }

	        private static void EnqueueMainThreadEvent(Action action)
	        {
	            if (action == null)
	                return;

	            var evt = new UnityEvent();
	            evt.AddListener(() => action());
	            MainThreadWorker.Enqueue(evt);
	        }

        public void SendCommand(string command)
        {
            if (sdk == IntPtr.Zero)
                return;

            if (string.IsNullOrEmpty(command))
                return;

            byte[] payload = Encoding.UTF8.GetBytes(command + "\0");
            AidlabAPI.AidlabSDK_send(payload, payload.Length, 0, sdk);
        }

        public void SendPayload(byte[] payload, int processId)
        {
            if (sdk == IntPtr.Zero)
                return;

            if (payload == null || payload.Length == 0)
                return;

            AidlabAPI.AidlabSDK_send(payload, payload.Length, processId, sdk);
        }

        private void DestroySdk()
        {
            if (sdk != IntPtr.Zero)
            {
                AidlabAPI.AidlabSDK_destroy(sdk);
                sdk = IntPtr.Zero;
            }

            if (contextHandle.IsAllocated)
                contextHandle.Free();

            lock (txLock)
            {
                pendingFrames.Clear();
            }
            canSendFrame = true;
        }

        private void FlushPendingFrames()
        {
            if (!canSendFrame)
                return;

            byte[] frame = null;
            lock (txLock)
            {
                if (pendingFrames.Count > 0)
                    frame = pendingFrames.Dequeue();
            }

            if (frame == null)
                return;

            canSendFrame = false;
            if (frame.Length > 0)
            {
                if (enablePacketTrace)
                {
                    string hex = BuildHexPreview(frame, frame.Length, 24);
                    Debug.Log($"AidlabSDK TX frame {frame.Length}B: {hex}");
                }
            }
            bleConnector?.Write(frame);
        }

        public void OnAidlabDataReceived(byte[] data, int size)
        {
            receivedData = true;

            if (data == null || size <= 0)
                return;

            long packetIndex = Interlocked.Increment(ref debugRxPackets);
            Interlocked.Add(ref debugRxBytes, size);
            Interlocked.Exchange(ref debugLastRxAtMs, DebugNowMs());
            Volatile.Write(ref debugLastRxSize, size);

            if (enablePacketTrace && (packetIndex <= 5 || (packetIndex % 200) == 0))
            {
                string hex = BuildHexPreview(data, size, 32);
                if (packetIndex <= 5)
                    Debug.Log($"AidlabSDK RX[{packetIndex}] {size}B: {hex}");
            }

            if (sdk == IntPtr.Zero)
            {
                lock (earlyRxLock)
                {
                    if (earlyRxBytesBuffered + size <= EarlyRxMaxBytes)
                    {
                        var copy = new byte[size];
                        Buffer.BlockCopy(data, 0, copy, 0, size);
                        earlyRxChunks.Enqueue(copy);
                        earlyRxBytesBuffered += size;
                    }
                }
                return;
            }

            AidlabAPI.AidlabSDK_process_ble_chunk(data, size, sdk);
        }

        private static string BuildHexPreview(byte[] data, int size, int maxBytes)
        {
            if (data == null || size <= 0)
                return "";

            int len = Math.Min(size, Math.Max(0, maxBytes));
            var sb = new StringBuilder(len * 2 + 4);
            for (int i = 0; i < len; i++)
            {
                sb.Append(data[i].ToString("X2"));
            }
            if (len < size)
                sb.Append("…");
            return sb.ToString();
        }

        private void DebugMaybeLogNoSignals()
        {
            if (sdk == IntPtr.Zero)
                return;

            long collectAt = Interlocked.Read(ref debugCollectSentAtMs);
            if (collectAt <= 0)
                return;

            long now = DebugNowMs();
            if (now - collectAt < 2500)
                return;

            long rxPacketsSnapshot = Interlocked.Read(ref debugRxPackets);
            if (rxPacketsSnapshot <= 0)
                return;

            long updatesSnapshot = Interlocked.Read(ref debugSignalUpdates);
            if (updatesSnapshot > 0)
                return;

            long lastLogAt = Interlocked.Read(ref debugLastNoSignalLogAtMs);
            if (lastLogAt > 0 && now - lastLogAt < 5000)
                return;

            int lastSize = Volatile.Read(ref debugLastRxSize);
            long lastRxAt = Interlocked.Read(ref debugLastRxAtMs);
            long ageMs = lastRxAt > 0 ? (now - lastRxAt) : -1;

            Debug.LogWarning(
                $"AidlabSDK: RX={rxPacketsSnapshot} packets ({Interlocked.Read(ref debugRxBytes)}B) but no signal callbacks after collect. " +
                $"LastRX={lastSize}B ageMs={ageMs}"
            );
            Interlocked.Exchange(ref debugLastNoSignalLogAtMs, now);
        }

        internal static void DebugNotifySignalUpdated()
        {
            Interlocked.Increment(ref debugSignalUpdates);
        }

        private static AidlabSDK GetInstance(IntPtr context)
        {
            if (context == IntPtr.Zero)
                return null;

            GCHandle handle = GCHandle.FromIntPtr(context);
            return handle.Target as AidlabSDK;
        }

        private static void OnBleSend(IntPtr context, IntPtr data, int size)
        {
            AidlabSDK instance = GetInstance(context);
            if (instance == null)
                return;

            if (data == IntPtr.Zero || size <= 0)
                return;

            byte[] frame = new byte[size];
            Marshal.Copy(data, frame, 0, size);

            if (instance.enablePacketTrace)
            {
                string hex = BuildHexPreview(frame, size, 32);
                Debug.Log($"AidlabSDK: OnBleSend {size}B: {hex}");
            }

            lock (instance.txLock)
            {
                instance.pendingFrames.Enqueue(frame);
            }
        }

        private static void OnBleReady(IntPtr context)
        {
            AidlabSDK instance = GetInstance(context);
            if (instance == null)
                return;

            instance.canSendFrame = true;
            if (instance.enablePacketTrace)
                Debug.Log("AidlabSDK: BLE ready");
        }

        private static void OnPayload(IntPtr context, IntPtr process, IntPtr payload, UIntPtr payloadLength)
        {
            _ = context;
            _ = process;
            _ = payload;
            _ = payloadLength;
        }

        private static void OnLogMessage(IntPtr context, AidlabAPI.LogLevel level, IntPtr message)
        {
            if (message == IntPtr.Zero)
                return;

            string text = Marshal.PtrToStringAnsi(message);
            AidlabSDK instance = GetInstance(context);
            bool verbose = instance != null && instance.enablePacketTrace;
            switch (level)
            {
                case AidlabAPI.LogLevel.Debug:
                    if (verbose)
                        Debug.Log(text);
                    break;
                case AidlabAPI.LogLevel.Info:
                    if (verbose)
                        Debug.Log(text);
                    break;
                case AidlabAPI.LogLevel.Warn:
                    Debug.LogWarning(text);
                    break;
                case AidlabAPI.LogLevel.Error:
                    Debug.LogError(text);
                    break;
            }
        }

        private static void OnEcg(IntPtr context, ulong timestamp, float value) { _ = context; aidlabDelegate.DidReceiveECG(timestamp, value); }
        private static void OnRespiration(IntPtr context, ulong timestamp, float value) { _ = context; aidlabDelegate.DidReceiveRespiration(timestamp, value); }
        private static void OnTemperature(IntPtr context, ulong timestamp, float value) { _ = context; aidlabDelegate.DidReceiveSkinTemperature(timestamp, value); }
        private static void OnAccelerometer(IntPtr context, ulong timestamp, float ax, float ay, float az) { _ = context; aidlabDelegate.DidReceiveAccelerometer(timestamp, ax, ay, az); }
        private static void OnGyroscope(IntPtr context, ulong timestamp, float gx, float gy, float gz) { _ = context; aidlabDelegate.DidReceiveGyroscope(timestamp, gx, gy, gz); }
        private static void OnMagnetometer(IntPtr context, ulong timestamp, float mx, float my, float mz) { _ = context; aidlabDelegate.DidReceiveMagnetometer(timestamp, mx, my, mz); }
        private static void OnBattery(IntPtr context, byte stateOfCharge) { _ = context; aidlabDelegate.DidReceiveBatteryLevel(stateOfCharge); }
        private static void OnActivity(IntPtr context, ulong timestamp, ActivityType activity) { _ = context; aidlabDelegate.DidReceiveActivity(timestamp, activity); }
        private static void OnSteps(IntPtr context, ulong timestamp, ulong steps) { _ = context; aidlabDelegate.DidReceiveSteps(timestamp, steps); }
        private static void OnOrientation(IntPtr context, ulong timestamp, float roll, float pitch, float yaw) { _ = context; aidlabDelegate.DidReceiveOrientation(timestamp, roll, pitch, yaw); }
        private static void OnQuaternion(IntPtr context, ulong timestamp, float qw, float qx, float qy, float qz) { _ = context; aidlabDelegate.DidReceiveQuaternion(timestamp, qw, qx, qy, qz); }
        private static void OnRespirationRate(IntPtr context, ulong timestamp, uint value) { _ = context; aidlabDelegate.DidReceiveRespirationRate(timestamp, value); }
        private static void OnWearState(IntPtr context, WearState wearState)
        {
            AidlabSDK instance = GetInstance(context);
            if (instance != null)
            {
                Interlocked.Increment(ref instance.debugWearStateCallbacks);
                Interlocked.Exchange(ref instance.debugLastWearStateAtMs, DebugNowMs());
            }
            aidlabDelegate.WearStateDidChange(wearState);
        }

        private static void OnHeartRate(IntPtr context, ulong timestamp, int heartRate)
        {
            AidlabSDK instance = GetInstance(context);
            if (instance != null)
            {
                Interlocked.Increment(ref instance.debugHrCallbacks);
                Interlocked.Exchange(ref instance.debugLastHrAtMs, DebugNowMs());
            }
            aidlabDelegate.DidReceiveHeartRate(timestamp, heartRate);
        }

        private static void OnRr(IntPtr context, ulong timestamp, int rr)
        {
            AidlabSDK instance = GetInstance(context);
            if (instance != null)
            {
                Interlocked.Increment(ref instance.debugRrCallbacks);
                Interlocked.Exchange(ref instance.debugLastRrAtMs, DebugNowMs());
            }
            aidlabDelegate.DidReceiveRr(timestamp, rr);
        }
        private static void OnSoundVolume(IntPtr context, ulong timestamp, ushort value) { _ = context; aidlabDelegate.DidReceiveSoundVolume(timestamp, value); }
        private static void OnExercise(IntPtr context, Exercise exercise) { _ = context; aidlabDelegate.DidDetectExercise(exercise); }
        private static void OnUserEvent(IntPtr context, ulong timestamp) { _ = context; aidlabDelegate.DidDetectUserEvent(timestamp); }
        private static void OnPressure(IntPtr context, ulong timestamp, int value) { _ = context; aidlabDelegate.DidReceivePressure(timestamp, value); }
        private static void OnPressureWearState(IntPtr context, WearState wearState) { _ = context; aidlabDelegate.PressureWearStateDidChange(wearState); }
        private static void OnBodyPosition(IntPtr context, ulong timestamp, BodyPosition value) { _ = context; aidlabDelegate.DidReceiveBodyPosition(timestamp, value); }
        private static void OnSignalQuality(IntPtr context, ulong timestamp, byte value) { _ = context; aidlabDelegate.DidReceiveSignalQuality(timestamp, value); }
        private static void OnEda(IntPtr context, ulong timestamp, float conductance) { _ = context; aidlabDelegate.DidReceiveEda(timestamp, conductance); }
        private static void OnGps(IntPtr context, ulong timestamp, float lat, float lon, float alt, float speed, float heading, float hdop) { _ = context; aidlabDelegate.DidReceiveGps(timestamp, lat, lon, alt, speed, heading, hdop); }

        private static void OnSyncState(IntPtr context, SyncState syncState) { _ = context; aidlabSyncDelegate.SyncStateDidChange(syncState); }
        private static void OnUnsynchronizedSize(IntPtr context, uint size, float progress) { _ = context; aidlabSyncDelegate.DidReceiveUnsynchronizedSize(size, progress); }

        private static void OnPastEcg(IntPtr context, ulong timestamp, float value) { _ = context; aidlabSyncDelegate.DidReceivePastEcg(timestamp, value); }
        private static void OnPastRespiration(IntPtr context, ulong timestamp, float value) { _ = context; aidlabSyncDelegate.DidReceivePastRespiration(timestamp, value); }
        private static void OnPastTemperature(IntPtr context, ulong timestamp, float value) { _ = context; aidlabSyncDelegate.DidReceivePastTemperature(timestamp, value); }
        private static void OnPastHeartRate(IntPtr context, ulong timestamp, int value) { _ = context; aidlabSyncDelegate.DidReceivePastHeartRate(timestamp, value); }
        private static void OnPastRr(IntPtr context, ulong timestamp, int value) { _ = context; aidlabSyncDelegate.DidReceivePastRr(timestamp, value); }
        private static void OnPastActivity(IntPtr context, ulong timestamp, ActivityType value) { _ = context; aidlabSyncDelegate.DidReceivePastActivity(timestamp, value); }
        private static void OnPastRespirationRate(IntPtr context, ulong timestamp, uint value) { _ = context; aidlabSyncDelegate.DidReceivePastRespirationRate(timestamp, value); }
        private static void OnPastSteps(IntPtr context, ulong timestamp, ulong value) { _ = context; aidlabSyncDelegate.DidReceivePastSteps(timestamp, value); }
        private static void OnPastUserEvent(IntPtr context, ulong timestamp) { _ = context; aidlabSyncDelegate.DidReceivePastUserEvent(timestamp); }
        private static void OnPastSoundVolume(IntPtr context, ulong timestamp, ushort value) { _ = context; aidlabSyncDelegate.DidReceivePastSoundVolume(timestamp, value); }
        private static void OnPastPressure(IntPtr context, ulong timestamp, int value) { _ = context; aidlabSyncDelegate.DidReceivePastPressure(timestamp, value); }
        private static void OnPastAccelerometer(IntPtr context, ulong timestamp, float ax, float ay, float az) { _ = context; aidlabSyncDelegate.DidReceivePastAccelerometer(timestamp, ax, ay, az); }
        private static void OnPastGyroscope(IntPtr context, ulong timestamp, float gx, float gy, float gz) { _ = context; aidlabSyncDelegate.DidReceivePastGyroscope(timestamp, gx, gy, gz); }
        private static void OnPastQuaternion(IntPtr context, ulong timestamp, float qw, float qx, float qy, float qz) { _ = context; aidlabSyncDelegate.DidReceivePastQuaternion(timestamp, qw, qx, qy, qz); }
        private static void OnPastOrientation(IntPtr context, ulong timestamp, float roll, float pitch, float yaw) { _ = context; aidlabSyncDelegate.DidReceivePastOrientation(timestamp, roll, pitch, yaw); }
        private static void OnPastMagnetometer(IntPtr context, ulong timestamp, float mx, float my, float mz) { _ = context; aidlabSyncDelegate.DidReceivePastMagnetometer(timestamp, mx, my, mz); }
        private static void OnPastBodyPosition(IntPtr context, ulong timestamp, BodyPosition value) { _ = context; aidlabSyncDelegate.DidReceivePastBodyPosition(timestamp, value); }
        private static void OnPastSignalQuality(IntPtr context, ulong timestamp, byte value) { _ = context; aidlabSyncDelegate.DidReceivePastSignalQuality(timestamp, value); }
        private static void OnPastEda(IntPtr context, ulong timestamp, float conductance) { _ = context; aidlabSyncDelegate.DidReceivePastEda(timestamp, conductance); }
        private static void OnPastGps(IntPtr context, ulong timestamp, float lat, float lon, float alt, float speed, float heading, float hdop) { _ = context; aidlabSyncDelegate.DidReceivePastGps(timestamp, lat, lon, alt, speed, heading, hdop); }
    }
}
