using System.Collections.Generic;
using System.Text;
using Aidlab;
using UnityEngine;
using UnityEngine.UI;

namespace Aidlab.Examples
{
		    public class Example : MonoBehaviour
		    {
		        [SerializeField] private bool autoInitSdk = true;
		        [SerializeField] private string targetDeviceName = "Aidlab";
		        [SerializeField] private Signal[] liveSignalsToCollect = { Signal.Ecg, Signal.Temperature, Signal.HeartRate, Signal.Rr };
		        [SerializeField] private Signal[] syncSignalsToStore = new Signal[0];
		        [SerializeField] private float refreshIntervalSeconds = 0.25f;
		        [SerializeField] private bool showDiagnostics = false;

        private AidlabSDK sdk;
        private bool subscribed;

        private Text headerText;
        private Text statusText;
        private Text devicesText;
        private Text signalsText;

        private float lastRefresh;

        private void Awake()
        {
            CreateOverlay();
        }

        private void Start()
        {
            EnsureSdkInstance();
            SubscribeSignalsOnce();
        }

		        private void Update()
		        {
		            EnsureSdkInstance();
		            SubscribeSignalsOnce();

		            if (sdk != null && sdk.TargetDeviceName != targetDeviceName)
		            {
		                sdk.SetTargetDeviceName(targetDeviceName);
		            }
		            if (sdk != null &&
		                (!SignalsEqual(sdk.LiveSignalsToCollect, liveSignalsToCollect) ||
		                 !SignalsEqual(sdk.SyncSignalsToStore, syncSignalsToStore)))
		            {
		                sdk.SetCollectSignals(liveSignalsToCollect, syncSignalsToStore);
		            }

            if (Time.unscaledTime - lastRefresh < refreshIntervalSeconds)
                return;

            lastRefresh = Time.unscaledTime;
            RefreshOverlay();
        }

        private void EnsureSdkInstance()
        {
            if (sdk != null)
                return;

            sdk = FindObjectOfType<AidlabSDK>();
            if (sdk != null)
                return;

            if (!autoInitSdk)
                return;

            AidlabSDK.init(targetDeviceName);
            sdk = FindObjectOfType<AidlabSDK>();
            if (sdk == null)
            {
                autoInitSdk = false;
            }
        }

        private void SubscribeSignalsOnce()
        {
            if (subscribed)
                return;

            subscribed = true;

            AidlabSDK.aidlabDelegate.temperature.Subscribe(RefreshOverlay);
            AidlabSDK.aidlabDelegate.wearState.Subscribe(RefreshOverlay);
            AidlabSDK.aidlabDelegate.heartRate.Subscribe(RefreshOverlay);
            AidlabSDK.aidlabDelegate.respiration.Subscribe(RefreshOverlay);
            AidlabSDK.aidlabDelegate.signalQuality.Subscribe(RefreshOverlay);
            AidlabSDK.aidlabDelegate.eda.Subscribe(RefreshOverlay);

            AidlabSDK.aidlabSyncDelegate.unsynchronizedSize.Subscribe(RefreshOverlay);
            AidlabSDK.aidlabSyncDelegate.syncState.Subscribe(RefreshOverlay);
        }

        private void CreateOverlay()
        {
            var canvasGo = new GameObject("AidlabExampleCanvas");
            var canvas = canvasGo.AddComponent<Canvas>();
            canvas.renderMode = RenderMode.ScreenSpaceOverlay;
            canvasGo.AddComponent<CanvasScaler>();
            canvasGo.AddComponent<GraphicRaycaster>();
            DontDestroyOnLoad(canvasGo);

            var panelGo = new GameObject("Panel");
            panelGo.transform.SetParent(canvasGo.transform, false);
            var panel = panelGo.AddComponent<Image>();
            panel.color = new Color(0f, 0f, 0f, 0.75f);
            var panelRect = panelGo.GetComponent<RectTransform>();
            panelRect.anchorMin = new Vector2(0f, 1f);
            panelRect.anchorMax = new Vector2(0f, 1f);
            panelRect.pivot = new Vector2(0f, 1f);
            panelRect.anchoredPosition = new Vector2(12f, -12f);
            panelRect.sizeDelta = new Vector2(640f, 420f);

            var layout = panelGo.AddComponent<VerticalLayoutGroup>();
            layout.padding = new RectOffset(12, 12, 12, 12);
            layout.spacing = 8f;
            layout.childControlHeight = true;
            layout.childControlWidth = true;
            layout.childForceExpandHeight = false;
            layout.childForceExpandWidth = true;

            // Unity 6 removed Arial.ttf as a built-in font; LegacyRuntime.ttf is the supported fallback.
            var font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");

            headerText = CreateText(panelGo.transform, font, 18, FontStyle.Bold);
            statusText = CreateText(panelGo.transform, font, 14, FontStyle.Normal);
            devicesText = CreateText(panelGo.transform, font, 14, FontStyle.Normal);
            signalsText = CreateText(panelGo.transform, font, 14, FontStyle.Normal);

            headerText.text = "Aidlab Unity Example (Desktop)";
            statusText.text = "";
            devicesText.text = "";
            signalsText.text = "";
        }

        private static Text CreateText(Transform parent, Font font, int fontSize, FontStyle style)
        {
            var go = new GameObject("Text");
            go.transform.SetParent(parent, false);
            var text = go.AddComponent<Text>();
            text.font = font;
            text.fontSize = fontSize;
            text.fontStyle = style;
            text.color = Color.white;
            text.alignment = TextAnchor.UpperLeft;
            text.horizontalOverflow = HorizontalWrapMode.Wrap;
            text.verticalOverflow = VerticalWrapMode.Overflow;
            var rect = go.GetComponent<RectTransform>();
            rect.sizeDelta = new Vector2(0f, 0f);
            return text;
        }

        private void RefreshOverlay()
        {
            if (headerText == null)
                return;

            var status = new StringBuilder();
	            status.AppendLine($"Platform: {Application.platform}");
		            status.AppendLine($"Target name: {(sdk != null ? sdk.TargetDeviceName : "n/a")}");
		            status.AppendLine($"Live: {FormatSignals(liveSignalsToCollect)}");
		            status.AppendLine($"Sync: {FormatSignals(syncSignalsToStore)}");
		            status.AppendLine($"Transport: {(sdk != null ? sdk.TransportStatus : "n/a")}");
            if (sdk != null && !string.IsNullOrEmpty(sdk.TransportLastError))
                status.AppendLine($"BLE error: {sdk.TransportLastError}");

            status.AppendLine($"SDK initialized: {(sdk != null && sdk.IsSdkInitialized ? "yes" : "no")}");
            status.AppendLine($"Firmware: {(sdk != null ? sdk.firmwareRevisionStr : "")}");
            status.AppendLine($"Hardware: {(sdk != null ? sdk.hardwareRevisionStr : "")}");
            status.AppendLine($"Last packet: {(sdk != null && sdk.SecondsSinceLastPacket >= 0 ? $"{sdk.SecondsSinceLastPacket:0.0}s ago" : "never")}");

            if (sdk != null && showDiagnostics)
            {
                long rxPackets = sdk.DebugRxPackets;
                long rxBytes = sdk.DebugRxBytes;
                long updates = sdk.DebugSignalUpdates;
                long lastRxAt = sdk.DebugLastRxAtMs;
                long ageMs = lastRxAt > 0 ? (AidlabSDK.DebugNowMs() - lastRxAt) : -1;
                long hrAt = sdk.DebugLastHrAtMs;
                long rrAt = sdk.DebugLastRrAtMs;
                long wearAt = sdk.DebugLastWearStateAtMs;

                status.AppendLine("Diagnostics:");
                status.AppendLine($"RX: {rxPackets} packets, {rxBytes} bytes; signal callbacks: {updates}; lastRxAgeMs={(ageMs >= 0 ? ageMs : -1)}");
                status.AppendLine($"HR callbacks: {sdk.DebugHrCallbacks} (last {(hrAt > 0 ? (AidlabSDK.DebugNowMs() - hrAt) : -1)} ms)");
                status.AppendLine($"RR callbacks: {sdk.DebugRrCallbacks} (last {(rrAt > 0 ? (AidlabSDK.DebugNowMs() - rrAt) : -1)} ms)");
                status.AppendLine($"Wear callbacks: {sdk.DebugWearStateCallbacks} (last {(wearAt > 0 ? (AidlabSDK.DebugNowMs() - wearAt) : -1)} ms)");
            }
            statusText.text = status.ToString();

            devicesText.text = BuildDevicesText();
            signalsText.text = BuildSignalsText();
        }

        private string BuildDevicesText()
        {
            if (sdk == null)
                return "Devices: (SDK not created yet)";

            if (Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXEditor)
            {
                string state = sdk.IsSdkInitialized ? "connected" : "scanning";
                return $"Devices: (macOS: scan list not exposed; auto-connect target=\"{sdk.TargetDeviceName}\"; state={state})";
            }

            if (Application.platform != RuntimePlatform.WindowsPlayer && Application.platform != RuntimePlatform.WindowsEditor)
            {
                return $"Devices: (unsupported platform: {Application.platform})";
            }

            List<BLEApi.DeviceUpdate> devices = sdk.GetKnownDevicesSnapshot();
            if (devices.Count == 0)
                return "Devices: (scanning...)";

            var sb = new StringBuilder();
            sb.AppendLine($"Devices ({devices.Count}):");
            int shown = 0;
            foreach (var dev in devices)
            {
                if (shown++ >= 10)
                {
                    sb.AppendLine("…");
                    break;
                }
                sb.AppendLine($"- {dev.name} [{(dev.isConnectable ? "connectable" : "no")}] id={dev.id}");
            }
            return sb.ToString();
        }

	        private static string BuildSignalsText()
	        {
            var d = AidlabSDK.aidlabDelegate;
            var s = AidlabSDK.aidlabSyncDelegate;

            var sb = new StringBuilder();
            sb.AppendLine("Signals (latest):");
            sb.AppendLine($"Temp: {d.temperature.value:0.00} °C");
            sb.AppendLine($"Wear: {d.wearState.value}");
	            sb.AppendLine($"HR: {d.heartRate.value} bpm");
	            sb.AppendLine($"RR: {d.rr.value} ms");
	            sb.AppendLine($"Resp: {d.respiration.value:0.000}");
	            sb.AppendLine($"EDA: {d.eda.value:0.000} µS");
	            sb.AppendLine($"Signal quality: {d.signalQuality.value}");
            sb.AppendLine($"Sync state: {s.syncState.value}");
            sb.AppendLine($"Unsynced: {s.unsynchronizedSize.a} bytes, {s.unsynchronizedSize.b:0.0} B/s");
	            return sb.ToString();
	        }

	        private static string FormatSignals(Signal[] signals)
	        {
	            if (signals == null || signals.Length == 0)
	                return "(none)";
	            return string.Join(", ", signals);
	        }

	        private static bool SignalsEqual(Signal[] a, Signal[] b)
	        {
	            if (ReferenceEquals(a, b))
	                return true;
	            if (a == null || b == null)
	                return false;
	            if (a.Length != b.Length)
	                return false;
	            for (int i = 0; i < a.Length; i++)
	            {
	                if (a[i] != b[i])
	                    return false;
	            }
	            return true;
	        }
	    }
}
