using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using EasyChart;

namespace EasyChart.Editor
{
    public partial class EasyChartLibraryWindow
    {
        private const string JsonPanelCollapsedPrefsKey = "EasyChart.LibraryWindow.JsonPanelCollapsed";
        private bool _jsonPanelCollapsed;
        private bool _jsonExampleDirtyByUser;

        private enum JsonDatasMode
        {
            Values,
            Standard,
            Full
        }

        private static readonly List<string> JsonDatasModeChoices = new List<string>
        {
            "Values",
            "Standard",
            "Full"
        };

        private JsonDatasMode _jsonDatasMode = JsonDatasMode.Standard;
        private PopupField<string> _jsonDatasModeDropdown;

        private struct JsonExampleModeOption
        {
            public string label;
            public JsonExampleMode mode;

            public JsonExampleModeOption(string label, JsonExampleMode mode)
            {
                this.label = label;
                this.mode = mode;
            }
        }

        private static readonly List<JsonExampleModeOption> JsonExampleModeOptions = new List<JsonExampleModeOption>
        {
            new JsonExampleModeOption("Lite", JsonExampleMode.Lite_Index),
            new JsonExampleModeOption("Standard / ID", JsonExampleMode.Lite_ID),
            new JsonExampleModeOption("Standard / Default", JsonExampleMode.Standard),
            new JsonExampleModeOption("Standard / With Axes", JsonExampleMode.Standard_Axis),
            new JsonExampleModeOption("Full", JsonExampleMode.Full)
        };

        private static readonly List<string> JsonExampleModeChoices = new List<string>
        {
            "Lite",
            "Standard / ID",
            "Standard / Default",
            "Standard / With Axes",
            "Full"
        };

        private void BuildInjectionJsonPanel(VisualElement leftPanel)
        {
            if (leftPanel == null) return;

            _jsonPanelCollapsed = EditorPrefs.GetBool(JsonPanelCollapsedPrefsKey, false);

            _jsonExampleContainer = new VisualElement();
            void ApplyJsonPanelHeight()
            {
                _jsonExampleContainer.style.height = _jsonPanelCollapsed ? 200 : 520;
            }

            ApplyJsonPanelHeight();
            _jsonExampleContainer.style.flexShrink = 0;
            _jsonExampleContainer.style.flexDirection = FlexDirection.Column;
            _jsonExampleContainer.style.minHeight = 0;
            _jsonExampleContainer.style.borderTopWidth = 1;
            _jsonExampleContainer.style.borderTopColor = new Color(0.15f, 0.15f, 0.15f);
            _jsonExampleContainer.style.backgroundColor = new Color(0.20f, 0.20f, 0.20f);
            _jsonExampleContainer.style.paddingLeft = 6;
            _jsonExampleContainer.style.paddingRight = 6;
            _jsonExampleContainer.style.paddingTop = 6;
            _jsonExampleContainer.style.paddingBottom = 6;

            var jsonHeaderRow = new VisualElement();
            jsonHeaderRow.style.flexDirection = FlexDirection.Row;
            jsonHeaderRow.style.alignItems = Align.Center;
            jsonHeaderRow.style.paddingLeft = 10;
            jsonHeaderRow.style.paddingRight = 10;
            jsonHeaderRow.style.paddingTop = 5;
            jsonHeaderRow.style.paddingBottom = 5;
            jsonHeaderRow.style.backgroundColor = new Color(0.25f, 0.25f, 0.25f);

            var jsonHeader = new Label("JSON Injection");
            jsonHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
            jsonHeaderRow.Add(jsonHeader);

            var jsonHeaderSpacer = new VisualElement();
            jsonHeaderSpacer.style.flexGrow = 1;
            jsonHeaderRow.Add(jsonHeaderSpacer);

            EnsureSharedIconsLoaded();

            Button jsonCollapseBtn = null;
            jsonCollapseBtn = new Button(() =>
            {
                _jsonPanelCollapsed = !_jsonPanelCollapsed;
                EditorPrefs.SetBool(JsonPanelCollapsedPrefsKey, _jsonPanelCollapsed);
                jsonCollapseBtn.text = _jsonPanelCollapsed ? "Max" : "Min";
                ApplyJsonPanelHeight();
            });
            jsonCollapseBtn.text = _jsonPanelCollapsed ? "Max" : "Min";
            jsonCollapseBtn.tooltip = "Toggle panel height";
            jsonCollapseBtn.style.height = 18;
            jsonCollapseBtn.style.minWidth = 44;
            jsonCollapseBtn.style.marginLeft = 6;
            jsonHeaderRow.Add(jsonCollapseBtn);

            _jsonApplyToChartButton = CreateClickableIconImage(_applyToChartIcon, "ApplyToChart", () => ApplyInjectionJsonToSelectedProfile());
            _jsonApplyToChartButton.style.marginLeft = 6;
            _jsonApplyToChartButton.style.marginBottom = 0;
            jsonHeaderRow.Add(_jsonApplyToChartButton);

            _jsonExampleContainer.Add(jsonHeaderRow);

            var jsonBtnRow = new VisualElement();
            jsonBtnRow.style.flexDirection = FlexDirection.Row;
            jsonBtnRow.style.marginTop = 6;
            jsonBtnRow.style.paddingRight = 6;
            jsonBtnRow.style.flexShrink = 0;
            jsonBtnRow.style.flexWrap = Wrap.Wrap;

            void UpdateApiToggleIcon()
            {
                if (_jsonApiToggleButton == null) return;
                _jsonApiToggleButton.image = _jsonUseApiEnvelope ? _apiOnIcon : _apiOffIcon;
            }

            _jsonApiToggleButton = CreateClickableIconImage(_apiOffIcon, "API Envelope", () =>
            {
                _jsonUseApiEnvelope = !_jsonUseApiEnvelope;
                UpdateApiToggleIcon();
                _jsonExampleDirtyByUser = false;
                UpdateInjectionJsonExample(forceOverwrite: true);
            });
            _jsonApiToggleButton.style.marginBottom = 4;
            jsonBtnRow.Add(_jsonApiToggleButton);

            UpdateApiToggleIcon();

            _jsonModeDropdown = new PopupField<string>(JsonExampleModeChoices, 0);
            _jsonModeDropdown.tooltip = "Feed Mode";
            _jsonModeDropdown.style.width = 150;
            _jsonModeDropdown.style.marginLeft = 6;
            _jsonModeDropdown.style.marginBottom = 4;
            _jsonModeDropdown.RegisterValueChangedCallback(evt =>
            {
                _jsonExampleMode = JsonExampleModeFromLabel(evt.newValue);
                _jsonExampleDirtyByUser = false;
                UpdateInjectionJsonExample(forceOverwrite: true);
            });

            InjectPopupIcon(_jsonModeDropdown, _feedIcon, "feed-icon");
            jsonBtnRow.Add(_jsonModeDropdown);

            _jsonDatasModeDropdown = new PopupField<string>(JsonDatasModeChoices, 1);
            _jsonDatasModeDropdown.tooltip = "Datas Format";
            _jsonDatasModeDropdown.style.width = 90;
            _jsonDatasModeDropdown.style.marginLeft = 6;
            _jsonDatasModeDropdown.style.marginBottom = 4;
            _jsonDatasModeDropdown.RegisterValueChangedCallback(evt =>
            {
                _jsonDatasMode = JsonDatasModeFromLabel(evt.newValue);
                _jsonExampleDirtyByUser = false;
                UpdateInjectionJsonExample(forceOverwrite: true);
            });

            InjectPopupIcon(_jsonDatasModeDropdown, _dataIcon, "data-icon");
            jsonBtnRow.Add(_jsonDatasModeDropdown);

            var jsonBtnSpacer = new VisualElement();
            jsonBtnSpacer.style.flexGrow = 1;
            jsonBtnRow.Add(jsonBtnSpacer);

            _jsonCopyButton = CreateClickableIconImage(_copyIcon, "Copy", () =>
            {
                if (_jsonExampleField == null) return;
                GUIUtility.systemCopyBuffer = _jsonExampleField.value;
            });
            _jsonCopyButton.style.marginLeft = 6;
            _jsonCopyButton.style.marginBottom = 4;
            jsonBtnRow.Add(_jsonCopyButton);

            _jsonExampleContainer.Add(jsonBtnRow);

            _jsonExampleScroll = new ScrollView(ScrollViewMode.Vertical);
            _jsonExampleScroll.style.flexGrow = 1;
            _jsonExampleScroll.style.flexShrink = 1;
            _jsonExampleScroll.style.flexBasis = 0;
            _jsonExampleScroll.style.minHeight = 0;
            _jsonExampleScroll.style.paddingLeft = 2;
            _jsonExampleScroll.style.paddingRight = 2;
            _jsonExampleScroll.style.paddingTop = 2;
            _jsonExampleScroll.style.paddingBottom = 2;
            _jsonExampleScroll.style.marginBottom = 0;
            _jsonExampleScroll.verticalScrollerVisibility = ScrollerVisibility.AlwaysVisible;
            _jsonExampleContainer.Add(_jsonExampleScroll);

            _jsonExampleScroll.contentContainer.style.flexGrow = 0;
            _jsonExampleScroll.contentContainer.style.flexShrink = 0;
            _jsonExampleScroll.contentContainer.style.minHeight = 0;

            _jsonExampleField = new TextField();
            _jsonExampleField.multiline = true;
            _jsonExampleField.isReadOnly = false;
            _jsonExampleField.style.flexGrow = 0;
            _jsonExampleField.style.flexShrink = 0;
            _jsonExampleField.style.minHeight = 0;
            _jsonExampleField.style.whiteSpace = WhiteSpace.Normal;
            _jsonExampleField.style.unityTextAlign = TextAnchor.UpperLeft;
            _jsonExampleField.RegisterValueChangedCallback(_ => { _jsonExampleDirtyByUser = true; });
            _jsonExampleScroll.Add(_jsonExampleField);

            leftPanel.Add(_jsonExampleContainer);

            UpdateJsonModeDropdown();
            _jsonExampleDirtyByUser = false;
            UpdateInjectionJsonExample(forceOverwrite: true);
        }

        private static void InjectPopupIcon(PopupField<string> popup, Texture2D icon, string iconElementName)
        {
            if (popup == null) return;
            if (icon == null) return;

            var input = popup.Q<VisualElement>(className: "unity-base-popup-field__input");
            if (input == null) return;

            if (!string.IsNullOrEmpty(iconElementName) && input.Q<VisualElement>(iconElementName) != null) return;

            input.style.position = Position.Relative;
            input.style.paddingLeft = 22;

            var img = new Image();
            img.name = iconElementName;
            img.image = icon;
            img.scaleMode = ScaleMode.ScaleToFit;
            img.style.position = Position.Absolute;
            img.style.left = 4;
            img.style.top = 2;
            img.style.width = 16;
            img.style.height = 16;
            input.Add(img);
        }

        private void UpdateInjectionJsonExample(bool forceOverwrite = false)
        {
            if (_jsonExampleField == null) return;
            if (!forceOverwrite && _jsonExampleDirtyByUser) return;

            if (_selectedProfile == null)
            {
                _jsonExampleField.value = string.Empty;
                return;
            }

            if (_selectedProfile.EnsureRuntimeData())
            {
                EditorUtility.SetDirty(_selectedProfile);
            }

            _jsonChartId = _selectedProfile.chartId;

            var json = BuildInjectionJsonForSelectedProfile(_selectedProfile, _jsonChartId, _jsonExampleMode, _jsonDatasMode);
            _jsonExampleField.value = _jsonUseApiEnvelope ? WrapAsApiResponse(json) : json;
            _jsonExampleDirtyByUser = false;
        }

        private void ApplyInjectionJsonToSelectedProfile()
        {
            if (_selectedProfile == null) return;
            if (_jsonExampleField == null) return;

            if (_serializedProfile != null && _serializedProfile.hasModifiedProperties)
            {
                _serializedProfile.ApplyModifiedProperties();
            }

            string json = _jsonExampleField.value;
            if (string.IsNullOrWhiteSpace(json)) return;

            if (!TryDeserializeFeed(json, out var feed))
            {
                Debug.LogError("[EasyChartLibraryWindow] ApplyToChart failed: invalid JSON or unsupported format.");
                return;
            }

            bool allowMetaOverwrite = _jsonExampleMode == JsonExampleMode.Full;
            bool changed = ApplyFeedToProfile(_selectedProfile, feed, allowMetaOverwrite);
            if (changed)
            {
                EditorUtility.SetDirty(_selectedProfile);
                AssetDatabase.SaveAssets();

                _serializedProfile?.Update();
                ScheduleRefreshSeriesList();
                ScheduleUpdatePreview();

                _jsonExampleDirtyByUser = false;
            }
        }

        private static bool TryDeserializeFeed(string json, out ChartFeed feed)
        {
            feed = null;

            if (TryExtractWrappedDataJson(json, out var dataJson) && !string.IsNullOrEmpty(dataJson))
            {
                json = dataJson;
            }

            if (ShouldPreferFlexibleFeedParser(json))
            {
                if (TryDeserializeFeedFlexibleNewtonsoft(json, out feed))
                {
                    return true;
                }

                // Prevent falling back to typed deserializers when datas is in tuple/values form.
                // Those deserializers can silently produce default DataFeed entries, which then
                // overwrites chart data (e.g. heatmap points become [0,0,0]).
                return false;
            }

            try
            {
                var jsonConvertType = Type.GetType("Newtonsoft.Json.JsonConvert, Newtonsoft.Json")
                                     ?? Type.GetType("Newtonsoft.Json.JsonConvert, Unity.Newtonsoft.Json");
                if (jsonConvertType != null)
                {
                    var deserialize = jsonConvertType.GetMethod("DeserializeObject", new[] { typeof(string), typeof(Type) });
                    if (deserialize != null)
                    {
                        var obj = deserialize.Invoke(null, new object[] { json, typeof(ChartFeed) });
                        feed = obj as ChartFeed;
                        return feed != null;
                    }
                }
            }
            catch
            {
            }

            try
            {
                json = NormalizeJsonForUnityJsonUtility(json);
                feed = JsonUtility.FromJson<ChartFeed>(json);
                return feed != null;
            }
            catch
            {
                // Continue to flexible parse below.
            }

            return TryDeserializeFeedFlexibleNewtonsoft(json, out feed);
        }

        private static string WrapAsApiResponse(string payloadJson)
        {
            if (string.IsNullOrWhiteSpace(payloadJson)) return string.Empty;

            var payload = payloadJson.Trim();
            if (payload.StartsWith("{", StringComparison.Ordinal) && payload.IndexOf("\"code\"", StringComparison.Ordinal) >= 0)
            {
                return payloadJson;
            }

            if (payload.Contains("\n"))
            {
                payload = IndentLines(payload, 2);
                return "{\n  \"code\": 200,\n  \"message\": \"success\",\n  \"data\":\n" + payload + "\n}";
            }

            return "{\n  \"code\": 200,\n  \"message\": \"success\",\n  \"data\": " + payload + "\n}";
        }

        private static string IndentLines(string text, int spaces)
        {
            if (string.IsNullOrEmpty(text)) return text;
            string pad = new string(' ', Mathf.Max(0, spaces));
            var lines = text.Replace("\r\n", "\n").Split('\n');
            for (int i = 0; i < lines.Length; i++)
            {
                lines[i] = pad + lines[i];
            }
            return string.Join("\n", lines);
        }

        private static bool TryExtractWrappedDataJson(string json, out string dataJson)
        {
            dataJson = null;
            if (string.IsNullOrEmpty(json)) return false;

            int dataKey = json.IndexOf("\"data\"", StringComparison.Ordinal);
            if (dataKey < 0) return false;

            int colon = json.IndexOf(':', dataKey + 6);
            if (colon < 0) return false;

            int start = -1;
            for (int i = colon + 1; i < json.Length; i++)
            {
                char c = json[i];
                if (char.IsWhiteSpace(c)) continue;
                if (c == '{' || c == '[') { start = i; break; }
                return false;
            }

            if (start < 0) return false;
            char open = json[start];
            char close = open == '{' ? '}' : ']';

            bool inString = false;
            bool escaped = false;
            int depth = 0;
            for (int i = start; i < json.Length; i++)
            {
                char c = json[i];
                if (inString)
                {
                    if (escaped) { escaped = false; continue; }
                    if (c == '\\') { escaped = true; continue; }
                    if (c == '"') { inString = false; }
                    continue;
                }

                if (c == '"') { inString = true; continue; }
                if (c == open) depth++;
                else if (c == close)
                {
                    depth--;
                    if (depth == 0)
                    {
                        dataJson = json.Substring(start, i - start + 1);
                        return true;
                    }
                }
            }

            return false;
        }

        private static bool ShouldPreferFlexibleFeedParser(string json)
        {
            if (string.IsNullOrEmpty(json)) return false;

            int idx = 0;
            while (idx < json.Length)
            {
                idx = json.IndexOf("\"datas\"", idx, StringComparison.Ordinal);
                if (idx < 0) return false;

                int colon = json.IndexOf(':', idx + 6);
                if (colon < 0) return false;

                int openBracket = -1;
                for (int i = colon + 1; i < json.Length; i++)
                {
                    char c = json[i];
                    if (char.IsWhiteSpace(c)) continue;
                    if (c != '[') break;
                    openBracket = i;
                    break;
                }

                if (openBracket < 0)
                {
                    idx = colon + 1;
                    continue;
                }

                int j = openBracket + 1;
                while (j < json.Length && char.IsWhiteSpace(json[j])) j++;
                if (j >= json.Length) return false;

                char first = json[j];
                if (first == '{' || first == ']')
                {
                    idx = j + 1;
                    continue;
                }

                return true;
            }

            return false;
        }

        private static bool TryDeserializeFeedFlexibleNewtonsoft(string json, out ChartFeed feed)
        {
            feed = null;

            try
            {
                var jObjectType = Type.GetType("Newtonsoft.Json.Linq.JObject, Newtonsoft.Json")
                               ?? Type.GetType("Newtonsoft.Json.Linq.JObject, Unity.Newtonsoft.Json");
                var jArrayType = Type.GetType("Newtonsoft.Json.Linq.JArray, Newtonsoft.Json")
                              ?? Type.GetType("Newtonsoft.Json.Linq.JArray, Unity.Newtonsoft.Json");
                if (jObjectType == null || jArrayType == null) return false;

                var parse = jObjectType.GetMethod("Parse", new[] { typeof(string) });
                if (parse == null) return false;

                var root = parse.Invoke(null, new object[] { json });
                if (root == null) return false;

                feed = new ChartFeed();

                var itemProp = jObjectType.GetProperty("Item", new[] { typeof(string) });
                if (itemProp == null) return false;

                string ReadString(object obj, string key)
                {
                    if (obj == null) return null;
                    var token = itemProp.GetValue(obj, new object[] { key });
                    if (token == null) return null;
                    var s = token.ToString();
                    if (string.IsNullOrEmpty(s)) return null;
                    if (s.Length >= 2 && s[0] == '"' && s[s.Length - 1] == '"') s = s.Substring(1, s.Length - 2);
                    return s;
                }

                float ReadFloat(object obj, string key, float defaultValue = 0f)
                {
                    var s = ReadString(obj, key);
                    if (string.IsNullOrEmpty(s)) return defaultValue;
                    if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v)) return v;
                    return defaultValue;
                }

                bool ReadBool(object obj, string key, bool defaultValue = false)
                {
                    var s = ReadString(obj, key);
                    if (string.IsNullOrEmpty(s)) return defaultValue;
                    if (bool.TryParse(s, out var v)) return v;
                    return defaultValue;
                }

                object GetToken(object obj, string key)
                {
                    if (obj == null) return null;
                    return itemProp.GetValue(obj, new object[] { key });
                }

                var rootChartId = ReadString(root, "chartId");
                var rootChartName = ReadString(root, "chartName");
                feed.chartId = rootChartId;
                feed.chartName = rootChartName;

                // Axes
                var axesToken = GetToken(root, "axes");
                if (axesToken != null && jArrayType.IsInstanceOfType(axesToken))
                {
                    var axes = new List<AxisFeed>();
                    foreach (var axisObj in (System.Collections.IEnumerable)axesToken)
                    {
                        if (axisObj == null) continue;

                        var axisIdStr = ReadString(axisObj, "axisId");
                        if (!Enum.TryParse(axisIdStr, ignoreCase: true, out AxisId axisId)) continue;

                        var labelsToken = GetToken(axisObj, "labels");
                        string[] labels = null;
                        if (labelsToken != null && jArrayType.IsInstanceOfType(labelsToken))
                        {
                            var list = new List<string>();
                            foreach (var l in (System.Collections.IEnumerable)labelsToken)
                            {
                                if (l == null) continue;
                                var s = l.ToString();
                                if (s.Length >= 2 && s[0] == '"' && s[s.Length - 1] == '"') s = s.Substring(1, s.Length - 2);
                                list.Add(s);
                            }
                            labels = list.ToArray();
                        }

                        axes.Add(new AxisFeed { axisId = axisId, labels = labels });
                    }
                    feed.axes = axes.Count > 0 ? axes.ToArray() : null;
                }

                // Series
                var seriesToken = GetToken(root, "series");
                if (seriesToken != null && jArrayType.IsInstanceOfType(seriesToken))
                {
                    var series = new List<SerieFeed>();
                    foreach (var serieObj in (System.Collections.IEnumerable)seriesToken)
                    {
                        if (serieObj == null) continue;

                        var sf = new SerieFeed();
                        sf.serieId = ReadString(serieObj, "serieId");
                        sf.name = ReadString(serieObj, "name");

                        var typeStr = ReadString(serieObj, "type");
                        if (!string.IsNullOrEmpty(typeStr) && int.TryParse(typeStr, out var tInt))
                        {
                            sf.type = (SerieType)tInt;
                        }
                        else if (!string.IsNullOrEmpty(typeStr) && Enum.TryParse(typeStr, ignoreCase: true, out SerieType tEnum))
                        {
                            sf.type = tEnum;
                        }

                        var datasToken = GetToken(serieObj, "datas");
                        if (datasToken != null && jArrayType.IsInstanceOfType(datasToken))
                        {
                            var datas = new List<DataFeed>();

                            object first = null;
                            foreach (var tmp in (System.Collections.IEnumerable)datasToken) { first = tmp; break; }

                            // Values mode: datas: [1,2,3]
                            if (first != null)
                            {
                                var firstText = first.ToString();
                                bool firstIsObject = firstText.StartsWith("{");
                                bool firstIsArray = firstText.StartsWith("[");

                                if (!firstIsObject && !firstIsArray)
                                {
                                    int idx = 0;
                                    foreach (var vToken in (System.Collections.IEnumerable)datasToken)
                                    {
                                        if (vToken == null) { idx++; continue; }
                                        var vs = vToken.ToString();
                                        if (!float.TryParse(vs, NumberStyles.Float, CultureInfo.InvariantCulture, out var vv)) vv = 0f;
                                        datas.Add(new DataFeed { x = idx, y = 0f, value = vv });
                                        idx++;
                                    }
                                }
                                else if (firstIsArray)
                                {
                                    // Tuple mode: datas: [[x,value], [x,value]] or [[x,y,value], ...]
                                    foreach (var tupleToken in (System.Collections.IEnumerable)datasToken)
                                    {
                                        if (tupleToken == null) continue;
                                        var tupleText = tupleToken.ToString();
                                        if (string.IsNullOrEmpty(tupleText)) continue;
                                        tupleText = tupleText.Trim();
                                        if (tupleText.Length < 2) continue;
                                        if (!tupleText.StartsWith("[") || !tupleText.EndsWith("]")) continue;
                                        tupleText = tupleText.Substring(1, tupleText.Length - 2);
                                        var parts = tupleText.Split(',');
                                        if (parts.Length < 2) continue;
                                        float tx = 0f;
                                        float ty = 0f;
                                        float tv = 0f;
                                        float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out tx);
                                        if (parts.Length == 2)
                                        {
                                            float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out tv);
                                        }
                                        else
                                        {
                                            float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out ty);
                                            float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out tv);
                                        }
                                        datas.Add(new DataFeed { x = tx, y = ty, value = tv });
                                    }
                                }
                                else
                                {
                                    // Standard/Full object mode
                                    foreach (var dpObj in (System.Collections.IEnumerable)datasToken)
                                    {
                                        if (dpObj == null) continue;
                                        var df = new DataFeed();
                                        df.id = ReadString(dpObj, "id");
                                        df.x = ReadFloat(dpObj, "x", 0f);
                                        df.y = ReadFloat(dpObj, "y", 0f);
                                        df.z = ReadFloat(dpObj, "z", 0f);
                                        df.value = ReadFloat(dpObj, "value", 0f);
                                        df.name = ReadString(dpObj, "name");
                                        df.useColor = ReadBool(dpObj, "useColor", false);
                                        if (df.useColor)
                                        {
                                            var colorToken = GetToken(dpObj, "color");
                                            if (colorToken != null)
                                            {
                                                df.color = new Color(
                                                    ReadFloat(colorToken, "r", 1f),
                                                    ReadFloat(colorToken, "g", 1f),
                                                    ReadFloat(colorToken, "b", 1f),
                                                    ReadFloat(colorToken, "a", 1f));
                                            }
                                        }
                                        datas.Add(df);
                                    }
                                }
                            }

                            sf.datas = datas.Count > 0 ? datas.ToArray() : null;
                        }

                        series.Add(sf);
                    }
                    feed.series = series.Count > 0 ? series.ToArray() : null;
                }

                return feed != null;
            }
            catch
            {
                feed = null;
                return false;
            }
        }

        private static string NormalizeJsonForUnityJsonUtility(string json)
        {
            if (string.IsNullOrEmpty(json)) return json;

            // Unity JsonUtility cannot parse enum values from strings.
            // Convert: "type": "Line" -> "type": 0
            json = Regex.Replace(json, "\\\"type\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"", match =>
            {
                var label = match.Groups[1].Value;
                if (Enum.TryParse(label, ignoreCase: true, out SerieType parsed))
                {
                    return "\"type\": " + ((int)parsed).ToString();
                }

                return match.Value;
            });

            // Convert: "axisId": "XBottom" -> "axisId": 0
            json = Regex.Replace(json, "\\\"axisId\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"", match =>
            {
                var label = match.Groups[1].Value;
                if (Enum.TryParse(label, ignoreCase: true, out AxisId parsed))
                {
                    return "\"axisId\": " + ((int)parsed).ToString();
                }

                return match.Value;
            });

            return json;
        }

        private static string ReplaceSerieTypeNumbersWithNames(string json)
        {
            if (string.IsNullOrEmpty(json)) return json;

            // Convert: "type": 0 -> "type": "Line"
            return Regex.Replace(json, "\\\"type\\\"\\s*:\\s*(\\d+)", match =>
            {
                if (!int.TryParse(match.Groups[1].Value, out var intValue)) return match.Value;
                var name = ((SerieType)intValue).ToString();
                return "\"type\": \"" + name + "\"";
            });
        }

        private static bool ApplyFeedToProfile(ChartProfile profile, ChartFeed feed, bool allowMetaOverwrite)
        {
            if (profile == null || feed == null) return false;

            bool changed = false;
            if (profile.EnsureRuntimeData()) changed = true;

            if (allowMetaOverwrite)
            {
                if (!string.IsNullOrEmpty(feed.chartId) && !string.Equals(profile.chartId, feed.chartId, StringComparison.Ordinal))
                {
                    profile.chartId = feed.chartId;
                    changed = true;
                }
                if (!string.IsNullOrEmpty(feed.chartName) && !string.Equals(profile.chartName, feed.chartName, StringComparison.Ordinal))
                {
                    profile.chartName = feed.chartName;
                    changed = true;
                }
            }

            if (feed.axes != null && feed.axes.Length > 0)
            {
                if (profile.axes == null) { profile.axes = new List<AxisConfig>(); changed = true; }

                for (int i = 0; i < feed.axes.Length; i++)
                {
                    var af = feed.axes[i];
                    if (af == null) continue;

                    AxisConfig axis = null;
                    for (int ai = 0; ai < profile.axes.Count; ai++)
                    {
                        var a = profile.axes[ai];
                        if (a != null && a.id == af.axisId) { axis = a; break; }
                    }

                    if (axis == null)
                    {
                        axis = new AxisConfig { id = af.axisId };
                        profile.axes.Add(axis);
                        changed = true;
                    }

                    if (af.labels != null)
                    {
                        axis.axisType = AxisType.Category;
                        if (axis.labels == null) { axis.labels = new List<string>(); changed = true; }

                        axis.labels.Clear();
                        axis.labels.AddRange(af.labels);
                        changed = true;
                    }
                }
            }

            if (feed.series != null && feed.series.Length > 0)
            {
                if (profile.series == null) { profile.series = new List<Serie>(); changed = true; }

                for (int i = 0; i < feed.series.Length; i++)
                {
                    var sf = feed.series[i];
                    if (sf == null) continue;

                    bool indexMode = string.IsNullOrEmpty(sf.serieId) && string.IsNullOrEmpty(sf.name);

                    Serie serie = null;
                    if (!string.IsNullOrEmpty(sf.serieId))
                    {
                        for (int si = 0; si < profile.series.Count; si++)
                        {
                            var s = profile.series[si];
                            if (s != null && string.Equals(s.id, sf.serieId, StringComparison.Ordinal)) { serie = s; break; }
                        }
                    }
                    if (serie == null && !string.IsNullOrEmpty(sf.name))
                    {
                        for (int si = 0; si < profile.series.Count; si++)
                        {
                            var s = profile.series[si];
                            if (s != null && string.Equals(s.name, sf.name, StringComparison.Ordinal)) { serie = s; break; }
                        }
                    }

                    if (serie == null && indexMode && i >= 0 && i < profile.series.Count)
                    {
                        serie = profile.series[i];
                    }

                    if (serie == null)
                    {
                        if (!allowMetaOverwrite && !indexMode) continue;

                        serie = new Serie { name = string.IsNullOrEmpty(sf.name) ? $"Series {i + 1}" : sf.name, type = sf.type, visible = true, seriesData = new List<SeriesData>() };
                        if (!string.IsNullOrEmpty(sf.serieId)) serie.id = sf.serieId;
                        serie.EnsureIntegrity();
                        profile.series.Add(serie);
                        changed = true;
                    }

                    if (allowMetaOverwrite && !indexMode)
                    {
                        if (!string.IsNullOrEmpty(sf.serieId) && string.IsNullOrEmpty(serie.id)) { serie.id = sf.serieId; changed = true; }
                        if (!string.IsNullOrEmpty(sf.name) && !string.Equals(serie.name, sf.name, StringComparison.Ordinal)) { serie.name = sf.name; changed = true; }
                        if (serie.type != sf.type) { serie.SetType(sf.type); changed = true; }
                    }

                    if (serie.seriesData == null) { serie.seriesData = new List<SeriesData>(); changed = true; }

                    if (sf.datas != null)
                    {
                        EnsureSeriesDataCount(serie.seriesData, sf.datas.Length);
                        for (int pi = 0; pi < sf.datas.Length; pi++)
                        {
                            var df = sf.datas[pi];
                            if (df == null) continue;

                            var p = serie.seriesData[pi] ?? (serie.seriesData[pi] = new SeriesData());

                            if (!string.IsNullOrEmpty(df.id)) p.id = df.id;
                            p.x = df.x;
                            p.y = df.y;
                            p.z = df.z;
                            p.value = df.value;

                            if (!string.IsNullOrEmpty(df.name)) p.name = df.name;
                            p.useColor = df.useColor;
                            if (df.useColor) p.color = df.color;
                        }
                        changed = true;
                    }

                    if (serie.EnsureIntegrity()) changed = true;
                }
            }

            if (profile.EnsureRuntimeData()) changed = true;
            return changed;
        }

        private static void EnsureSeriesDataCount(List<SeriesData> list, int count)
        {
            if (list == null) return;
            while (list.Count < count) list.Add(new SeriesData());
            if (list.Count > count) list.RemoveRange(count, list.Count - count);
        }

        private void UpdateJsonModeDropdown()
        {
            if (_jsonModeDropdown == null) return;

            var label = JsonExampleModeToLabel(_jsonExampleMode);
            _jsonModeDropdown.SetValueWithoutNotify(label);
        }

        private static string JsonExampleModeToLabel(JsonExampleMode mode)
        {
            for (int i = 0; i < JsonExampleModeOptions.Count; i++)
            {
                if (JsonExampleModeOptions[i].mode == mode) return JsonExampleModeOptions[i].label;
            }

            return JsonExampleModeOptions != null && JsonExampleModeOptions.Count > 0
                ? JsonExampleModeOptions[0].label
                : "Lite_Index";
        }

        private static JsonExampleMode JsonExampleModeFromLabel(string label)
        {
            if (string.IsNullOrEmpty(label)) return JsonExampleMode.Lite_Index;

            for (int i = 0; i < JsonExampleModeOptions.Count; i++)
            {
                if (string.Equals(JsonExampleModeOptions[i].label, label, StringComparison.OrdinalIgnoreCase))
                {
                    return JsonExampleModeOptions[i].mode;
                }
            }

            return JsonExampleMode.Lite_Index;
        }

        private static string BuildInjectionJsonForSelectedProfile(ChartProfile profile, string chartId, JsonExampleMode mode)
        {
            return BuildInjectionJsonForSelectedProfile(profile, chartId, mode, JsonDatasMode.Standard);
        }

        private static string BuildInjectionJsonForSelectedProfile(ChartProfile profile, string chartId, JsonExampleMode mode, JsonDatasMode datasMode)
        {
            if (profile == null) return string.Empty;

            if (mode == JsonExampleMode.Lite_Index) return BuildLiteIndexInjectionJson(profile, datasMode);
            if (mode == JsonExampleMode.Lite_Name) return BuildStandardInjectionJson(profile, datasMode);
            if (mode == JsonExampleMode.Lite_ID) return BuildLiteIdInjectionJson(profile, chartId, datasMode);
            if (mode == JsonExampleMode.Standard_Axis) return BuildStandardAxisInjectionJson(profile, datasMode);
            if (mode == JsonExampleMode.Standard) return BuildStandardInjectionJson(profile, datasMode);

            if (mode == JsonExampleMode.Full) return BuildFullInjectionJson(profile, chartId, datasMode);

            return string.Empty;
        }

        private static string BuildFullInjectionJson(ChartProfile profile, string chartId, JsonDatasMode datasMode)
        {
            if (profile == null) return string.Empty;

            string resolvedChartId = string.IsNullOrWhiteSpace(chartId) ? profile.chartId : chartId;
            string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;

            var sb = new System.Text.StringBuilder(8192);
            sb.Append("{\n");
            sb.Append("  \"chartId\": \"").Append(JsonEscape(resolvedChartId)).Append("\",");
            sb.Append("\n  \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");

            // Axes (Category only)
            if (profile.axes != null)
            {
                var axisList = new List<AxisConfig>();
                for (int i = 0; i < profile.axes.Count; i++)
                {
                    var a = profile.axes[i];
                    if (a == null) continue;
                    if (a.axisType != AxisType.Category) continue;
                    if (a.labels == null || a.labels.Count == 0) continue;
                    axisList.Add(a);
                }

                if (axisList.Count > 0)
                {
                    sb.Append(",\n  \"axes\": [\n");
                    for (int i = 0; i < axisList.Count; i++)
                    {
                        var a = axisList[i];
                        if (i > 0) sb.Append(",\n");

                        sb.Append("    {\n");
                        sb.Append("      \"axisId\": \"").Append(JsonEscape(a.id.ToString())).Append("\",\n");
                        sb.Append("      \"labels\": [");
                        for (int li = 0; li < a.labels.Count; li++)
                        {
                            if (li > 0) sb.Append(", ");
                            sb.Append("\"").Append(JsonEscape(a.labels[li])).Append("\"");
                        }
                        sb.Append("]\n");
                        sb.Append("    }");
                    }
                    sb.Append("\n  ]");
                }
            }

            if (profile.series != null && profile.series.Count > 0)
            {
                sb.Append(",\n  \"series\": [\n");
                for (int i = 0; i < profile.series.Count; i++)
                {
                    var s = profile.series[i];
                    if (s == null) continue;
                    if (i > 0) sb.Append(",\n");

                    sb.Append("    {\n");
                    sb.Append("      \"serieId\": \"").Append(JsonEscape(s.id)).Append("\",\n");
                    sb.Append("      \"name\": \"").Append(JsonEscape(s.name)).Append("\",\n");
                    sb.Append("      \"type\": \"").Append(JsonEscape(s.type.ToString())).Append("\"");

                    int dataCount = s.seriesData != null ? s.seriesData.Count : 0;
                    if (dataCount > 0)
                    {
                        bool isHeatmap = s.type == SerieType.Heatmap;
                        if (datasMode == JsonDatasMode.Values)
                        {
                            sb.Append(",\n      \"datas\": [");
                        }
                        else
                        {
                            sb.Append(",\n      \"datas\": [\n");
                        }
                        for (int pi = 0; pi < dataCount; pi++)
                        {
                            var dp = s.seriesData[pi];
                            if (pi > 0)
                            {
                                if (datasMode == JsonDatasMode.Values) sb.Append(", ");
                                else sb.Append(",\n");
                            }

                            float x = dp != null ? dp.x : 0f;
                            float y = dp != null ? dp.y : 0f;
                            float v = dp != null ? dp.value : 0f;

                            if (datasMode == JsonDatasMode.Values && !isHeatmap)
                            {
                                sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
                            }
                            else if (datasMode == JsonDatasMode.Values && isHeatmap)
                            {
                                sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append("]");
                            }
                            else
                            {
                                sb.Append("        { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));

                                if (dp != null)
                                {
                                    if (!string.IsNullOrEmpty(dp.id)) sb.Append(", \"id\": \"").Append(JsonEscape(dp.id)).Append("\"");
                                    if (!string.IsNullOrEmpty(dp.name)) sb.Append(", \"name\": \"").Append(JsonEscape(dp.name)).Append("\"");
                                    if (dp.useColor)
                                    {
                                        sb.Append(", \"useColor\": true");
                                        sb.Append(", \"color\": { \"r\": ").Append(dp.color.r.ToString("R", CultureInfo.InvariantCulture));
                                        sb.Append(", \"g\": ").Append(dp.color.g.ToString("R", CultureInfo.InvariantCulture));
                                        sb.Append(", \"b\": ").Append(dp.color.b.ToString("R", CultureInfo.InvariantCulture));
                                        sb.Append(", \"a\": ").Append(dp.color.a.ToString("R", CultureInfo.InvariantCulture));
                                        sb.Append(" }");
                                    }
                                }

                                sb.Append(" }");
                            }
                        }
                        if (datasMode == JsonDatasMode.Values) sb.Append("]");
                        else sb.Append("\n      ]");
                    }

                    sb.Append("\n    }");
                }
                sb.Append("\n  ]");
            }

            sb.Append("\n}");
            return sb.ToString();
        }

        private static string BuildLiteIndexInjectionJson(ChartProfile profile, JsonDatasMode datasMode)
        {
            if (profile == null) return string.Empty;

            string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;

            var sb = new System.Text.StringBuilder(2048);
            sb.Append("{\n");
            sb.Append("  \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");

            bool hasSeries = profile.series != null && profile.series.Count > 0;
            if (hasSeries)
            {
                sb.Append(",\n  \"series\": [\n");
                for (int i = 0; i < profile.series.Count; i++)
                {
                    var s = profile.series[i];
                    if (i > 0) sb.Append(",\n");

                    sb.Append("    {\n");

                    int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
                    if (dataCount > 0)
                    {
                        bool isHeatmap = s != null && s.type == SerieType.Heatmap;
                        if (datasMode == JsonDatasMode.Values)
                        {
                            sb.Append("      \"datas\": [");
                        }
                        else
                        {
                            sb.Append("      \"datas\": [\n");
                        }
                        for (int pi = 0; pi < dataCount; pi++)
                        {
                            var dp = s.seriesData[pi];
                            if (pi > 0)
                            {
                                if (datasMode == JsonDatasMode.Values) sb.Append(", ");
                                else sb.Append(",\n");
                            }

                            float x = isHeatmap ? (dp != null ? dp.x : 0f) : pi;
                            float y = dp != null ? dp.y : 0f;
                            float v = dp != null ? dp.value : 0f;

                            if (datasMode == JsonDatasMode.Values && !isHeatmap)
                            {
                                sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
                            }
                            else if (datasMode == JsonDatasMode.Values && isHeatmap)
                            {
                                sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append("]");
                            }
                            else
                            {
                                sb.Append("        { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(" }");
                            }
                        }
                        if (datasMode == JsonDatasMode.Values) sb.Append("]\n");
                        else sb.Append("\n      ]\n");
                    }

                    sb.Append("    }");
                }
                sb.Append("\n  ]");
            }

            sb.Append("\n}");
            return sb.ToString();
        }

        private static string BuildStandardInjectionJson(ChartProfile profile, JsonDatasMode datasMode)
        {
            if (profile == null) return string.Empty;

            string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;

            var sb = new System.Text.StringBuilder(4096);
            sb.Append("{\n");
            sb.Append("  \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");

            if (profile.series != null && profile.series.Count > 0)
            {
                sb.Append(",\n  \"series\": [\n");
                for (int i = 0; i < profile.series.Count; i++)
                {
                    var s = profile.series[i];
                    if (i > 0) sb.Append(",\n");

                    sb.Append("    {\n");
                    sb.Append("      \"name\": \"").Append(JsonEscape(s != null ? s.name : string.Empty)).Append("\"");

                    int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
                    if (dataCount > 0)
                    {
                        bool isHeatmap = s.type == SerieType.Heatmap;
                        if (datasMode == JsonDatasMode.Values)
                        {
                            sb.Append(",\n      \"datas\": [");
                        }
                        else
                        {
                            sb.Append(",\n      \"datas\": [\n");
                        }
                        for (int pi = 0; pi < dataCount; pi++)
                        {
                            var dp = s.seriesData[pi];
                            if (pi > 0)
                            {
                                if (datasMode == JsonDatasMode.Values) sb.Append(", ");
                                else sb.Append(",\n");
                            }
                            float x = dp != null ? dp.x : 0f;
                            float y = dp != null ? dp.y : 0f;
                            float v = dp != null ? dp.value : 0f;

                            if (datasMode == JsonDatasMode.Values && !isHeatmap)
                            {
                                sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
                            }
                            else if (datasMode == JsonDatasMode.Values && isHeatmap)
                            {
                                sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append("]");
                            }
                            else
                            {
                                sb.Append("        { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(" }");
                            }
                        }
                        if (datasMode == JsonDatasMode.Values) sb.Append("]");
                        else sb.Append("\n      ]");
                    }

                    sb.Append("\n    }");
                }
                sb.Append("\n  ]");
            }

            sb.Append("\n}");
            return sb.ToString();
        }

        private static string BuildLiteNameInjectionJson(ChartProfile profile, JsonDatasMode datasMode)
        {
            if (profile == null) return string.Empty;

            string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;

            var sb = new System.Text.StringBuilder(2048);
            sb.Append("{\n");
            sb.Append("  \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");

            bool hasSeries = profile.series != null && profile.series.Count > 0;
            if (hasSeries)
            {
                sb.Append(",\n  \"series\": [\n");
                for (int i = 0; i < profile.series.Count; i++)
                {
                    var s = profile.series[i];
                    if (i > 0) sb.Append(",\n");

                    sb.Append("    {\n");
                    sb.Append("      \"name\": \"").Append(JsonEscape(s != null ? s.name : string.Empty)).Append("\"");

                    int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
                    if (dataCount > 0)
                    {
                        bool isHeatmap = s != null && s.type == SerieType.Heatmap;
                        if (datasMode == JsonDatasMode.Values)
                        {
                            sb.Append(",\n      \"datas\": [");
                        }
                        else
                        {
                            sb.Append(",\n      \"datas\": [\n");
                        }
                        for (int pi = 0; pi < dataCount; pi++)
                        {
                            var dp = s.seriesData[pi];
                            if (pi > 0)
                            {
                                if (datasMode == JsonDatasMode.Values) sb.Append(", ");
                                else sb.Append(",\n");
                            }

                            float x = isHeatmap ? (dp != null ? dp.x : 0f) : pi;
                            float y = dp != null ? dp.y : 0f;
                            float v = dp != null ? dp.value : 0f;

                            if (datasMode == JsonDatasMode.Values && !isHeatmap)
                            {
                                sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
                            }
                            else if (datasMode == JsonDatasMode.Values && isHeatmap)
                            {
                                sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append("]");
                            }
                            else
                            {
                                sb.Append("        { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(" }");
                            }
                        }
                        if (datasMode == JsonDatasMode.Values) sb.Append("]\n");
                        else sb.Append("\n      ]\n");
                    }

                    sb.Append("    }");
                }
                sb.Append("\n  ]");
            }

            sb.Append("\n}");
            return sb.ToString();
        }

        private static string BuildLiteIdInjectionJson(ChartProfile profile, string chartId, JsonDatasMode datasMode)
        {
            if (profile == null) return string.Empty;

            string resolvedChartId = string.IsNullOrWhiteSpace(chartId) ? profile.chartId : chartId;

            var sb = new System.Text.StringBuilder(2048);
            sb.Append("{\n");
            sb.Append("  \"chartId\": \"").Append(JsonEscape(resolvedChartId)).Append("\"");

            bool hasSeries = profile.series != null && profile.series.Count > 0;
            if (hasSeries)
            {
                sb.Append(",\n  \"series\": [\n");
                for (int i = 0; i < profile.series.Count; i++)
                {
                    var s = profile.series[i];
                    if (i > 0) sb.Append(",\n");

                    sb.Append("    {\n");
                    sb.Append("      \"serieId\": \"").Append(JsonEscape(s != null ? s.id : string.Empty)).Append("\"");

                    int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
                    if (dataCount > 0)
                    {
                        bool isHeatmap = s != null && s.type == SerieType.Heatmap;
                        if (datasMode == JsonDatasMode.Values)
                        {
                            sb.Append(",\n      \"datas\": [");
                        }
                        else
                        {
                            sb.Append(",\n      \"datas\": [\n");
                        }
                        for (int pi = 0; pi < dataCount; pi++)
                        {
                            var dp = s.seriesData[pi];
                            if (pi > 0)
                            {
                                if (datasMode == JsonDatasMode.Values) sb.Append(", ");
                                else sb.Append(",\n");
                            }

                            float x = isHeatmap ? (dp != null ? dp.x : 0f) : pi;
                            float y = dp != null ? dp.y : 0f;
                            float v = dp != null ? dp.value : 0f;

                            if (datasMode == JsonDatasMode.Values)
                            {
                                if (isHeatmap)
                                {
                                    sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                    sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                    sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                    sb.Append("]");
                                }
                                else
                                {
                                    sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
                                }
                            }
                            else
                            {
                                sb.Append("        { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(" }");
                            }
                        }
                        if (datasMode == JsonDatasMode.Values) sb.Append("]");
                        else sb.Append("\n      ]");
                    }

                    sb.Append("\n    }");
                }
                sb.Append("\n  ]");
            }

            sb.Append("\n}");
            return sb.ToString();
        }

        private static string BuildStandardAxisInjectionJson(ChartProfile profile, JsonDatasMode datasMode)
        {
            if (profile == null) return string.Empty;

            string resolvedChartName = string.IsNullOrWhiteSpace(profile.chartName) ? profile.name : profile.chartName;

            var sb = new System.Text.StringBuilder(4096);
            sb.Append("{\n");
            sb.Append("  \"chartName\": \"").Append(JsonEscape(resolvedChartName)).Append("\"");

            // Axes (Category only)
            if (profile.axes != null)
            {
                var axisList = new List<AxisConfig>();
                for (int i = 0; i < profile.axes.Count; i++)
                {
                    var a = profile.axes[i];
                    if (a == null) continue;
                    if (a.axisType != AxisType.Category) continue;
                    if (a.labels == null || a.labels.Count == 0) continue;
                    axisList.Add(a);
                }

                if (axisList.Count > 0)
                {
                    sb.Append(",\n  \"axes\": [\n");
                    for (int i = 0; i < axisList.Count; i++)
                    {
                        var a = axisList[i];
                        if (i > 0) sb.Append(",\n");

                        sb.Append("    {\n");
                        sb.Append("      \"axisId\": \"").Append(JsonEscape(a.id.ToString())).Append("\",\n");
                        sb.Append("      \"labels\": [");
                        for (int li = 0; li < a.labels.Count; li++)
                        {
                            if (li > 0) sb.Append(", ");
                            sb.Append("\"").Append(JsonEscape(a.labels[li])).Append("\"");
                        }
                        sb.Append("]\n");
                        sb.Append("    }");
                    }
                    sb.Append("\n  ]");
                }
            }

            // Series points (name aligned)
            if (profile.series != null && profile.series.Count > 0)
            {
                sb.Append(",\n  \"series\": [\n");
                for (int i = 0; i < profile.series.Count; i++)
                {
                    var s = profile.series[i];
                    if (i > 0) sb.Append(",\n");

                    sb.Append("    {\n");
                    sb.Append("      \"name\": \"").Append(JsonEscape(s != null ? s.name : string.Empty)).Append("\"");

                    int dataCount = s != null && s.seriesData != null ? s.seriesData.Count : 0;
                    if (dataCount > 0)
                    {
                        bool isHeatmap = s.type == SerieType.Heatmap;
                        if (datasMode == JsonDatasMode.Values)
                        {
                            sb.Append(",\n      \"datas\": [");
                        }
                        else
                        {
                            sb.Append(",\n      \"datas\": [\n");
                        }
                        for (int pi = 0; pi < dataCount; pi++)
                        {
                            var dp = s.seriesData[pi];
                            if (pi > 0)
                            {
                                if (datasMode == JsonDatasMode.Values) sb.Append(", ");
                                else sb.Append(",\n");
                            }
                            float x = dp != null ? dp.x : 0f;
                            float y = dp != null ? dp.y : 0f;
                            float v = dp != null ? dp.value : 0f;

                            if (datasMode == JsonDatasMode.Values && !isHeatmap)
                            {
                                sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
                            }
                            else if (datasMode == JsonDatasMode.Values && isHeatmap)
                            {
                                sb.Append("[").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(",").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append("]");
                            }
                            else
                            {
                                sb.Append("        { \"x\": ").Append(x.ToString("R", CultureInfo.InvariantCulture));
                                if (isHeatmap) sb.Append(", \"y\": ").Append(y.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(", \"value\": ").Append(v.ToString("R", CultureInfo.InvariantCulture));
                                sb.Append(" }");
                            }
                        }
                        if (datasMode == JsonDatasMode.Values) sb.Append("]");
                        else sb.Append("\n      ]");
                    }

                    sb.Append("\n    }");
                }
                sb.Append("\n  ]");
            }

            sb.Append("\n}");
            return sb.ToString();
        }

        private static string JsonEscape(string s)
        {
            if (string.IsNullOrEmpty(s)) return string.Empty;

            return s
                .Replace("\\", "\\\\")
                .Replace("\"", "\\\"")
                .Replace("\r", "\\r")
                .Replace("\n", "\\n")
                .Replace("\t", "\\t");
        }

        private static string FloatArrayToJson(float[] values)
        {
            if (values == null || values.Length == 0) return "[]";

            var sb = new System.Text.StringBuilder(values.Length * 6);
            sb.Append("[");
            for (int i = 0; i < values.Length; i++)
            {
                if (i > 0) sb.Append(", ");
                sb.Append(values[i].ToString("R", CultureInfo.InvariantCulture));
            }
            sb.Append("]");
            return sb.ToString();
        }

        private static JsonDatasMode JsonDatasModeFromLabel(string label)
        {
            if (string.IsNullOrEmpty(label)) return JsonDatasMode.Standard;
            if (string.Equals(label, "Values", StringComparison.OrdinalIgnoreCase)) return JsonDatasMode.Values;
            if (string.Equals(label, "Full", StringComparison.OrdinalIgnoreCase)) return JsonDatasMode.Full;
            return JsonDatasMode.Standard;
        }
    }
}
