using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UVC.Log;

namespace UVC.Data
{
    /// <summary>
    /// 서로 다른 JSON 데이터 구조 간에 매핑 기능을 제공하는 클래스입니다.
    /// </summary>
    /// <remarks>
    /// 이 클래스는 JSON 데이터를 DataObject와 DataArray 형식으로 변환하며, 중첩된 구조(nested structure)도 처리할 수 있습니다.
    /// 소스 JSON 객체의 속성을 Mask 객체에 정의된 타입에 따라 적절히 변환합니다.
    /// </remarks>
    /// <example>
    /// 기본 사용 예시:
    /// <code>
    /// // 소스 JSON 데이터
    /// var sourceJson = JObject.Parse(@"{
    ///     ""name"": ""김철수"",
    ///     ""age"": 30,
    ///     ""isActive"": true
    /// }");
    /// 
    /// // Mask 객체 (타입 지정용)
    /// var maskJson = DataMask.Parse(@"{
    ///     ""name"": """",
    ///     ""age"": 0,
    ///     ""isActive"": false
    /// }");
    /// maskJson.ObjectIdKey = "name"; // DataObject의 Id로 사용할 속성 지정
    /// 
    /// var mapper = new DataMapper(maskJson);
    /// DataObject result = mapper.Map(sourceJson);
    /// 
    /// // result는 원본과 동일한 구조이며 각 속성이 Mask에 따라 타입 변환됨
    /// </code>
    /// </example>
    public class DataMapper
    {

        // 재귀 호출 제한하기 위한 설정 추가
        private int maxRecursionDepth = 10;

        /// <summary>
        /// 재귀 최대 깊이를 설정하거나 가져옵니다. 이 값을 초과하는 중첩 객체는 간소화된 처리가 적용됩니다.
        /// </summary>
        public int MaxRecursionDepth
        {
            get => maxRecursionDepth;
            set => maxRecursionDepth = Math.Max(1, value); // 최소 1 이상 보장
        }

        /// <summary>
        /// 타입 변환을 위한 마스크 객체
        /// </summary>
        private DataMask mask;

        /// <summary>
        /// 병렬 처리를 적용할 배열의 최소 크기
        /// </summary>
        private int parallelProcessingThreshold = 1000;

        /// <summary>
        /// 병렬 처리를 적용할 배열의 최소 크기를 설정하거나 가져옵니다.
        /// </summary>
        public int ParallelProcessingThreshold
        {
            get => parallelProcessingThreshold;
            set => parallelProcessingThreshold = Math.Max(10, value); // 최소 10 이상 보장
        }

        /// <summary>
        /// 매핑 중 발생한 변환 오류를 추적하는 딕셔너리
        /// </summary>
        private Dictionary<string, Exception> conversionErrors;

        /// <summary>
        /// 매핑 중 발생한 모든 변환 오류에 접근할 수 있는 읽기 전용 속성
        /// </summary>
        public IReadOnlyDictionary<string, Exception> ConversionErrors => conversionErrors;

        /// <summary>
        /// 대용량 JSON 데이터를 스트리밍 방식으로 처리할지 여부를 나타내는 속성입니다.
        /// </summary>
        public bool SupportsStreamParsing { get; internal set; } = true;

        /// <summary>
        /// 대용량 JSON 스트림을 판단 할때 스트림 길이가 이 값보다 크면 스트리밍 방식으로 처리합니다.
        /// </summary>
        public int SupportsStreamLength { get; internal set; } = 10000;

        /// <summary>
        /// DataMapper 클래스의 새 인스턴스를 초기화합니다.
        /// </summary>
        /// <param name="mask">타입 변환을 위한 Mask JSON 객체</param>
        /// <remarks>
        /// Mask 객체는 원본 JSON 객체와 동일한 구조를 가질 필요는 없지만,
        /// 변환하려는 속성들에 대한 타입 정보를 제공해야 합니다.
        /// </remarks>
        public DataMapper(DataMask mask)
        {
            this.mask = mask ?? throw new ArgumentNullException(nameof(mask));
            this.conversionErrors = new Dictionary<string, Exception>();
        }

        /// <summary>
        /// 소스 객체를 Mask 객체를 기반으로 매핑하여 새로운 DataObject를 생성합니다.
        /// </summary>
        /// <param name="source">매핑할 원본 JSON 객체</param>
        /// <returns>매핑된 DataObject 객체</returns>
        /// <example>
        /// <code>
        /// var mapper = new DataMapper(maskJson);
        /// DataObject result = mapper.Map(sourceJson);
        /// ULog.Debug(result["name"].ToString()); // "김철수"
        /// ULog.Debug(result["age"].ToObject<int>()); // 30
        /// </code>
        /// </example>
        public DataObject Map(JObject source)
        {
            return MapObject(source, mask);
        }

        /// <summary>
        /// 소스 배열을 Mask 객체를 기반으로 매핑하여 새로운 DataArray를 생성합니다.
        /// </summary>
        /// <param name="source">매핑할 원본 JSON 배열</param>
        /// <returns>매핑된 DataArray 객체</returns>
        /// <remarks>
        /// 이 메서드는 Mask 객체를 JArray로 변환하여 소스 배열의 각 항목을 매핑합니다.
        /// Mask 배열이 비어있으면 원본 배열의 각 항목을 그대로 변환하고,
        /// 그렇지 않으면 Mask 배열이 하나인 경우 첫 번째 항목을 템플릿으로 사용하고
        /// 하나 이상인 경우 Mask 배열 개수에 맞춰 템플릿으로 사용(배열 길이가 3이고, 소스 배열 길이가 5일때, 소스배열 3까지만 매핑) 합니다.
        /// </remarks>
        /// <example>
        /// <code>
        /// var sourceArray = JArray.Parse(@"[
        ///     { ""name"": ""김철수"", ""age"": 30 },
        ///     { ""name"": ""이영희"", ""age"": 25 }
        /// ]");
        /// 
        /// var maskJson = DataMask.Parse(@"{
        ///     ""name"": """",
        ///     ""age"": 0
        /// }");
        /// maskJson.ObjectIdKey = "name"; // DataObject의 Id로 사용할 속성 지정
        /// 
        /// var mapper = new DataMapper(maskJson);
        /// DataArray result = mapper.Map(sourceArray);
        /// // result는 원본 배열과 동일한 구조의 DataArray
        /// </code>
        /// </example>
        public DataArray Map(JArray source)
        {
            List<DataMask> arr = new List<DataMask>() { mask };
            return MapArray(source, arr);
        }

        /// <summary>
        /// 대용량 JSON 데이터를 스트리밍 방식으로 매핑합니다.
        /// </summary>
        /// <param name="jsonStream">JSON 데이터 스트림</param>
        /// <returns>매핑된 DataObject</returns>
        public DataObject MapObjectStream(System.IO.Stream jsonStream)
        {
            if (jsonStream == null)
                throw new ArgumentNullException(nameof(jsonStream));

            // 스트림 처리 최적화를 위해 청크 단위로 읽을 수 있지만,
            // 현재는 Newtonsoft.Json의 기본 역직렬화 사용
            using (var reader = new Newtonsoft.Json.JsonTextReader(new System.IO.StreamReader(jsonStream)))
            {
                // 청크 읽기 설정 - 메모리 사용량 최적화
                reader.SupportMultipleContent = true;

                var serializer = new Newtonsoft.Json.JsonSerializer();
                var sourceObject = serializer.Deserialize<JObject>(reader);

                return Map(sourceObject);
            }
        }

        /// <summary>
        /// 대용량 JSON 데이터를 스트리밍 방식으로 매핑합니다.
        /// </summary>
        /// <param name="jsonStream">JSON 데이터 스트림</param>
        /// <returns>매핑된 DataObject</returns>
        public DataArray MapArrayStream(System.IO.Stream jsonStream)
        {
            if (jsonStream == null)
                throw new ArgumentNullException(nameof(jsonStream));

            using (var reader = new Newtonsoft.Json.JsonTextReader(new System.IO.StreamReader(jsonStream)))
            {
                // 청크 읽기 설정 - 메모리 사용량 최적화
                reader.SupportMultipleContent = true;

                var serializer = new Newtonsoft.Json.JsonSerializer();
                var sourceArray = serializer.Deserialize<JArray>(reader);

                return Map(sourceArray);
            }
        }

        /// <summary>
        /// 객체를 재귀적으로 매핑합니다.
        /// </summary>
        /// <param name="sourceObject">원본 JSON 객체</param>
        /// <param name="maskObject">Mask JSON 객체</param>
        /// <param name="depth">현재 재귀 깊이</param>
        /// <param name="path">현재 속성 경로(오류 추적용)</param>
        /// <returns>매핑된 DataObject 객체</returns>
        /// <remarks>
        /// 이 메서드는 중첩된 객체와 배열을 포함하여 JSON 구조를 재귀적으로 처리합니다.
        /// Mask 객체에 포함되지 않은 속성은 원본 값을 그대로 사용합니다.
        /// </remarks>
        /// <example>
        /// 중첩 객체 매핑 예시:
        /// <code>
        /// var sourceJson = JObject.Parse(@"{
        ///     ""user"": {
        ///         ""name"": ""김철수"",
        ///         ""address"": {
        ///             ""city"": ""서울"",
        ///             ""zipcode"": ""12345""
        ///         }
        ///     }
        /// }");
        /// 
        /// var maskJson = DataMask.Parse(@"{
        ///     ""user"": {
        ///         ""name"": """",
        ///         ""address"": {
        ///             ""city"": """",
        ///             ""zipcode"": """"
        ///         }
        ///     }
        /// }");
        /// maskJson.ObjectIdKey = "user"; // DataObject의 Id로 사용할 속성 지정
        /// 
        /// var mapper = new DataMapper(maskJson);
        /// var result = mapper.Map(sourceJson);
        /// // result는 sourceJson과 동일한 중첩 구조를 유지
        /// </code>
        /// </example>
        private DataObject MapObject(JObject sourceObject, DataMask maskObject, int depth = 0, string path = "")
        {
            if (maskObject == null) return new DataObject(sourceObject);

            // Mask가 비어있으면 원본 객체를 그대로 사용, 깊이 제한에 도달하면 간소화된 처리
            if (maskObject.Count == 0 || depth >= maxRecursionDepth)
            {
                DataObject dObj = new DataObject() { IdKey = maskObject.ObjectIdKey, Name = maskObject.ObjectName };
                // 속성 이름 변환 처리
                foreach (var property in sourceObject.Properties())
                {
                    string propertyName = property.Name;
                    if (maskObject.NamesForReplace != null && maskObject.NamesForReplace.ContainsKey(propertyName))
                    {
                        propertyName = maskObject.NamesForReplace[propertyName];
                    }
                    dObj[propertyName] = ConvertJTokenToObject(property.Value);
                }
                return dObj;
            }

            DataObject target = new DataObject() { IdKey = maskObject.ObjectIdKey, Name = maskObject.ObjectName };

            foreach (var property in sourceObject.Properties())
            {
                string propertyName = property.Name;
                string currentPath = string.IsNullOrEmpty(path) ? propertyName : $"{path}.{propertyName}";

                if (maskObject.ContainsKey(propertyName))
                {
                    object maskValue = maskObject[propertyName];
                    JToken sourceValue = property.Value;

                    // 속성 이름 변환 처리
                    if (maskObject.NamesForReplace != null && maskObject.NamesForReplace.ContainsKey(propertyName))
                    {
                        propertyName = maskObject.NamesForReplace[propertyName];
                    }

                    try
                    {
                        // 중첩된 객체 처리
                        if (sourceValue.Type == JTokenType.Object && maskValue is DataMask maskSubObject)
                        {
                            target[propertyName] = MapObject((JObject)sourceValue, maskSubObject, depth + 1, currentPath);
                        }
                        // 중첩된 배열 처리
                        else if (sourceValue.Type == JTokenType.Array && maskValue is List<DataMask> maskArrayTemplate)
                        {
                            var arr = MapArray((JArray)sourceValue, maskArrayTemplate, currentPath);
                            target[propertyName] = arr;
                        }
                        // 일반 속성 처리
                        else
                        {
                            MapProperty(propertyName, sourceValue, maskValue, target, currentPath);
                        }
                    }
                    catch (Exception ex)
                    {
                        // 예외 정보 저장 후 기본값 설정
                        conversionErrors[currentPath] = ex;
                        ULog.Warning($"매핑 중 오류 발생 - 경로: {currentPath}", ex);

                        // 마스크 값 타입에 맞는 기본값 설정
                        SetDefaultValueForProperty(propertyName, maskValue, target);
                    }
                }
                else
                {
                    // Mask에 없는 속성은 무시 (필요시 설정을 통해 포함 가능)
                    continue;
                }
            }

            return target;
        }

        /// <summary>
        /// 예외 발생 시 마스크 값 타입에 맞는 기본값을 설정합니다.
        /// </summary>
        private void SetDefaultValueForProperty(string propertyName, object maskValue, DataObject target)
        {
            if (maskValue is string)
            {
                target[propertyName] = string.Empty;
            }
            else if (maskValue is int || maskValue is long || maskValue is short || maskValue is byte)
            {
                target[propertyName] = 0;
            }
            else if (maskValue is double || maskValue is float || maskValue is decimal)
            {
                target[propertyName] = 0.0;
            }
            else if (maskValue is bool)
            {
                target[propertyName] = false;
            }
            else if (maskValue is DateTime)
            {
                target[propertyName] = null;
            }
            else if (maskValue is DataMask)
            {
                target[propertyName] = new DataObject();
            }
            else if (maskValue is List<DataMask>)
            {
                target[propertyName] = new DataArray();
            }
            else
            {
                target[propertyName] = null;
            }
        }

        /// <summary>
        /// JToken을 실제 객체로 변환하는 헬퍼 메서드
        /// </summary>
        /// <param name="token">변환할 JToken 객체</param>
        /// <returns>변환된 .NET 객체</returns>
        /// <remarks>
        /// 이 메서드는 JToken의 타입에 따라 적절한 .NET 타입의 객체로 변환합니다.
        /// 객체는 DataObject로, 배열은 DataArray로 변환되며, 기본 타입은 해당하는 .NET 타입으로 변환됩니다.
        /// </remarks>
        private object ConvertJTokenToObject(JToken token)
        {
            if (token == null) return null;

            switch (token.Type)
            {
                case JTokenType.Object:
                    return new DataObject((JObject)token);
                case JTokenType.Array:
                    JArray array = (JArray)token;
                    if (array.All(item => item.Type == JTokenType.Object))
                    {
                        return new DataArray(array);
                    }
                    return array.ToObject<object[]>();
                case JTokenType.Integer:
                    return token.ToObject<int>();
                case JTokenType.Float:
                    return token.ToObject<double>();
                case JTokenType.String:
                    return token.ToObject<string>();
                case JTokenType.Boolean:
                    return token.ToObject<bool>();
                case JTokenType.Date:
                    return token.ToObject<DateTime>();
                case JTokenType.Null:
                    return null;
                default:
                    return token.ToObject<object>();
            }
        }

        /// <summary>
        /// 배열을 재귀적으로 매핑합니다.
        /// </summary>
        /// <param name="sourceArray">원본 JSON 배열</param>
        /// <param name="maskArray">Mask JSON 배열</param>
        /// <param name="path">현재 속성 경로(오류 추적용)</param>
        /// <returns>매핑된 DataArray 객체</returns>
        /// <remarks>
        /// Mask 배열이 비어있으면, 원본 배열의 각 항목을 DataObject로 변환하여 복사합니다.
        /// 단순 값(문자열, 숫자 등)은 "value" 키를 가진 DataObject로 래핑됩니다.
        /// 
        /// Mask 배열이 비어있지 않으면, Mask 배열의 첫 번째 항목을 템플릿으로 사용하고
        /// 하나 이상인 경우 Mask 배열 개수에 맞춰 템플릿으로 사용(배열 길이가 3이고, 소스 배열 길이가 5일때, 소스배열 3까지만 매핑) 합니다.
        /// 원본 배열의 각 항목을 매핑합니다. 중첩 배열은 "items" 키를 가진 DataObject로 래핑됩니다.
        /// </remarks>
        /// <example>
        /// 배열 매핑 예시:
        /// <code>
        /// var sourceJson = JObject.Parse(@"{
        ///     ""contacts"": [
        ///         { ""type"": ""mobile"", ""number"": ""010-1234-5678"" },
        ///         { ""type"": ""home"", ""number"": ""02-123-4567"" }
        ///     ]
        /// }");
        /// 
        /// var maskJson = DataMask.Parse(@"{
        ///     ""contacts"": [
        ///         { ""type"": """", ""number"": """" }
        ///     ]
        /// }");
        /// maskJson.ObjectIdKey = "contacts"; // DataObject의 Id로 사용할 속성 지정
        /// 
        /// var mapper = new DataMapper(maskJson);
        /// var result = mapper.Map(sourceJson);
        /// // result.contacts는 원본 배열과 동일한 구조의 DataArray
        /// </code>
        /// </example>
        private DataArray MapArray(JArray sourceArray, List<DataMask> maskTemplates, string path = "")
        {
            // 빠른 초기 크기 할당으로 재할당 방지
            DataArray targetArray = new DataArray(sourceArray.Count);

            // 특정 크기 이상일 경우 병렬 처리 적용
            if (sourceArray.Count > parallelProcessingThreshold && maskTemplates.Count <= 1)
            {
                return MapArrayInParallel(sourceArray, maskTemplates, path);
            }

            // Mask 배열이 비어있으면 원본 배열을 그대로 사용
            if (maskTemplates == null || maskTemplates.Count == 0)
            {
                for (int i = 0; i < sourceArray.Count; i++)
                {
                    JToken sourceItem = sourceArray[i];
                    string itemPath = $"{path}[{i}]";

                    try
                    {
                        if (sourceItem.Type == JTokenType.Object)
                        {
                            targetArray.Add(new DataObject((JObject)sourceItem));
                        }
                        else
                        {
                            // DataObject가 아닌 경우, 새 DataObject를 만들고 값을 넣어줍니다
                            var dataObject = new DataObject();
                            dataObject.Add("value", ConvertJTokenToObject(sourceItem));
                            targetArray.Add(dataObject);
                        }
                    }
                    catch (Exception ex)
                    {
                        conversionErrors[itemPath] = ex;
                        ULog.Warning($"배열 항목 변환 오류 - 경로: {itemPath}", ex);

                        // 오류 발생 시 빈 객체 추가
                        targetArray.Add(new DataObject());
                    }
                }
                return targetArray;
            }

            // Mask 템플릿 사용 - 반복 사용 혹은 일치 사용
            for (int i = 0; i < sourceArray.Count; i++)
            {
                JToken sourceItem = sourceArray[i];
                string itemPath = $"{path}[{i}]";

                // 현재 인덱스에 맞는 마스크 템플릿 선택 (또는 첫 번째 템플릿 재사용)
                DataMask maskTemplate;
                if (maskTemplates.Count > 1)
                {
                    // maskTemplates.Count > 1이면서 소스 항목 개수 보다 적은 경우
                    // maskTemplates.Count 보다 큰 인덱스는 무시
                    // 예: maskTemplates.Count가 3이고, sourceArray.Count가 5인 경우
                    // 0, 1, 2 인덱스는 maskTemplates[0], maskTemplates[1], maskTemplates[2] 사용
                    // 3, 4 인덱스는 저장 않함
                    if (i >= maskTemplates.Count)
                        continue;
                    int templateIndex = Math.Min(i, maskTemplates.Count - 1);
                    maskTemplate = maskTemplates[templateIndex];
                }
                else
                {
                    // 단일 템플릿 반복 사용
                    maskTemplate = maskTemplates[0];
                }

                try
                {
                    if (sourceItem.Type == JTokenType.Object)
                    {
                        JObject sourceItem2 = (JObject)sourceItem;
                        targetArray.Add(MapObject(sourceItem2, maskTemplate, 0, itemPath));
                    }
                    else if (sourceItem.Type == JTokenType.Array)
                    {
                        // DataArray는 DataObject만 담을 수 있으므로, 배열을 처리하는 방식을 변경해야 함
                        var nestedArray = MapArray((JArray)sourceItem, maskTemplates, itemPath);
                        var container = new DataObject();
                        container.Add("items", nestedArray);
                        targetArray.Add(container);
                    }
                    else
                    {
                        // 단순 값을 DataObject로 래핑
                        var dataObject = new DataObject();
                        dataObject.Add("value", ConvertJTokenToObject(sourceItem));
                        targetArray.Add(dataObject);
                    }
                }
                catch (Exception ex)
                {
                    conversionErrors[itemPath] = ex;
                    ULog.Warning($"배열 항목 변환 오류 - 경로: {itemPath}", ex);

                    // 오류 발생 시 빈 객체 추가
                    targetArray.Add(new DataObject());
                }
            }

            return targetArray;
        }

        /// <summary>
        /// 대형 배열을 병렬로 처리하는 배열 매핑 메서드
        /// </summary>
        private DataArray MapArrayInParallel(JArray sourceArray, List<DataMask> maskTemplates, string path)
        {
            DataArray targetArray = new DataArray(sourceArray.Count);
            DataMask maskTemplate = maskTemplates.Count > 0 ? maskTemplates[0] : new DataMask();

            var parallelOptions = new ParallelOptions
            {
                MaxDegreeOfParallelism = Environment.ProcessorCount
            };

            // 결과를 담을 배열 (순서 보존을 위해)
            var results = new DataObject[sourceArray.Count];

            try
            {
                Parallel.For(0, sourceArray.Count, parallelOptions, i =>
                {
                    var sourceItem = sourceArray[i];
                    string itemPath = $"{path}[{i}]";

                    try
                    {
                        if (sourceItem.Type == JTokenType.Object && maskTemplate != null)
                        {
                            results[i] = MapObject((JObject)sourceItem, maskTemplate, 0, itemPath);
                        }
                        else
                        {
                            // 단순 값 처리
                            var dataObject = new DataObject();
                            dataObject.Add("value", ConvertJTokenToObject(sourceItem));
                            results[i] = dataObject;
                        }
                    }
                    catch (Exception ex)
                    {
                        lock (conversionErrors)
                        {
                            conversionErrors[itemPath] = ex;
                        }
                        ULog.Warning($"병렬 배열 처리 오류 - 경로: {itemPath}", ex);

                        // 오류 발생 시 빈 객체 생성
                        results[i] = new DataObject();
                    }
                });

                // 결과를 순서대로 추가
                targetArray.AddRange(results);
            }
            catch (Exception ex)
            {
                ULog.Error($"병렬 배열 매핑 중 치명적 오류 발생: {ex.Message}", ex);

                // 병렬 처리 실패 시 일반 순차 처리로 대체
                return MapArraySequential(sourceArray, maskTemplates, path);
            }

            return targetArray;
        }

        /// <summary>
        /// 병렬 처리가 실패할 경우 대체 사용할 순차적 배열 처리 메서드
        /// </summary>
        private DataArray MapArraySequential(JArray sourceArray, List<DataMask> maskTemplates, string path)
        {
            DataArray targetArray = new DataArray(sourceArray.Count);
            DataMask maskTemplate = maskTemplates.Count > 0 ? maskTemplates[0] : new DataMask();

            for (int i = 0; i < sourceArray.Count; i++)
            {
                JToken sourceItem = sourceArray[i];
                string itemPath = $"{path}[{i}]";

                try
                {
                    if (sourceItem.Type == JTokenType.Object)
                    {
                        targetArray.Add(MapObject((JObject)sourceItem, maskTemplate, 0, itemPath));
                    }
                    else
                    {
                        var dataObject = new DataObject();
                        dataObject.Add("value", ConvertJTokenToObject(sourceItem));
                        targetArray.Add(dataObject);
                    }
                }
                catch (Exception ex)
                {
                    conversionErrors[itemPath] = ex;
                    ULog.Warning($"순차 배열 처리 오류 - 경로: {itemPath}", ex);
                    targetArray.Add(new DataObject());
                }
            }

            return targetArray;
        }

        /// <summary>
        /// 개별 속성을 매핑합니다.
        /// </summary>
        /// <param name="propertyName">속성 이름</param>
        /// <param name="sourceValue">매핑할 원본 값</param>
        /// <param name="maskValue">타입을 결정하는 Mask 값</param>
        /// <param name="target">값을 추가할 대상 DataObject</param>
        /// <param name="path">현재 속성 경로(오류 추적용)</param>
        /// <remarks>
        /// 이 메서드는 Mask 값의 타입에 따라 원본 값을 적절한 타입으로 변환합니다.
        /// 
        /// 지원되는 타입:
        /// - 문자열 (string)
        /// - 정수 (int)
        /// - 실수 (double, float)
        /// - 불리언 (bool)
        /// - 날짜/시간 (DateTime)
        /// - 열거형 (Enum)
        /// - DataMap (문자열 매핑 딕셔너리)
        /// - DataMask (중첩된 마스크 객체)
        /// - List<DataMask> (배열 마스크)
        /// 
        /// 타입 변환이 불가능한 경우 원본 값이 그대로 사용됩니다.
        /// </remarks>
        /// <example>
        /// 다양한 타입 매핑 예시:
        /// <code>
        /// var sourceJson = JObject.Parse(@"{
        ///     ""name"": ""김철수"",
        ///     ""age"": 30,
        ///     ""height"": 175.5,
        ///     ""isActive"": true,
        ///     ""birthDate"": ""1990-01-01T00:00:00"",
        ///     ""status"": ""Active""
        /// }");
        /// 
        /// // Mask 객체 설정 (열거형 포함)
        /// var maskJson = new DataMask();
        /// maskJson["name"] = "";
        /// maskJson["age"] = 0;
        /// maskJson["height"] = 0.0;
        /// maskJson["isActive"] = false;
        /// maskJson["birthDate"] = JToken.FromObject(DateTime.Now);
        /// maskJson["status"] = JToken.FromObject(UserStatus.Inactive);
        /// 
        /// maskJson.ObjectIdKey = "name"; // DataObject의 Id로 사용할 속성 지정
        /// 
        /// var mapper = new DataMapper(maskJson);
        /// var result = mapper.Map(sourceJson);
        /// // result에는 모든 속성이 적절한 타입으로 변환됨
        /// </code>
        /// </example>
        private void MapProperty(string propertyName, JToken sourceValue, object maskValue, DataObject target, string path)
        {

            // 소스 값이 널이면 바로 널 설정
            if (sourceValue == null || sourceValue.Type == JTokenType.Null)
            {
                target[propertyName] = null;
                return;
            }

            // 타입별 매핑 처리를 분리된 메서드로 호출
            if (maskValue is string)
            {
                MapStringProperty(propertyName, sourceValue, target);
            }
            else if (maskValue is int || maskValue is long || maskValue is short || maskValue is byte)
            {
                MapIntegerProperty(propertyName, sourceValue, target);
            }
            else if (maskValue is double || maskValue is float || maskValue is decimal)
            {
                MapFloatingPointProperty(propertyName, sourceValue, target);
            }
            else if (maskValue is bool)
            {
                MapBooleanProperty(propertyName, sourceValue, target);
            }
            else if (maskValue is DateTime)
            {
                MapDateTimeProperty(propertyName, sourceValue, target);
            }
            else if (maskValue is Enum || (maskValue?.GetType()?.IsEnum ?? false))
            {
                MapEnumProperty(propertyName, sourceValue, maskValue, target);
            }
            else if (maskValue is Dictionary<string, object> dicObj && dicObj.GetType() == typeof(DataMask))
            {
                MapNestedObjectProperty(propertyName, sourceValue, (DataMask)maskValue, target, path);
            }
            else if (maskValue is List<DataMask>)
            {
                MapNestedArrayProperty(propertyName, sourceValue, (List<DataMask>)maskValue, target, path);
            }
            else if (maskValue is Dictionary<string, string> || maskValue is DataMap)
            {
                MapDataMapProperty(propertyName, sourceValue, maskValue, target);
            }
            else if (maskValue is JToken jToken)
            {
                MapJTokenProperty(propertyName, sourceValue, jToken, target);
            }
            else
            {
                // 기타 타입은 그대로 객체로 변환
                target[propertyName] = ConvertJTokenToObject(sourceValue);
            }
        }

        #region 타입별 속성 매핑 메서드

        /// <summary>
        /// 문자열 타입 속성 매핑
        /// </summary>
        private void MapStringProperty(string propertyName, JToken sourceValue, DataObject target)
        {
            if (sourceValue.Type == JTokenType.String || sourceValue.Type == JTokenType.Integer ||
                sourceValue.Type == JTokenType.Float || sourceValue.Type == JTokenType.Boolean)
            {
                target[propertyName] = sourceValue.ToObject<string>();
            }
            else
            {
                target[propertyName] = sourceValue.ToString();
            }
        }

        /// <summary>
        /// 정수 타입 속성 매핑
        /// </summary>
        private void MapIntegerProperty(string propertyName, JToken sourceValue, DataObject target)
        {
            if (sourceValue.Type == JTokenType.Integer || sourceValue.Type == JTokenType.Float ||
                sourceValue.Type == JTokenType.String)
            {
                try
                {
                    // 명시적으로 값 범위 검사를 통해 오버플로우 방지
                    var doubleValue = sourceValue.ToObject<double>();
                    if (doubleValue > int.MaxValue || doubleValue < int.MinValue)
                    {
                        throw new OverflowException($"값 {doubleValue}가 Int32 범위를 벗어남");
                    }

                    target[propertyName] = Convert.ToInt32(doubleValue);
                }
                catch (Exception)
                {
                    target[propertyName] = 0; // 변환 실패시 기본값
                }
            }
            else
            {
                target[propertyName] = 0;
            }
        }

        /// <summary>
        /// 부동소수점 타입 속성 매핑
        /// </summary>
        private void MapFloatingPointProperty(string propertyName, JToken sourceValue, DataObject target)
        {
            if (sourceValue.Type == JTokenType.Float || sourceValue.Type == JTokenType.Integer ||
                sourceValue.Type == JTokenType.String)
            {
                try
                {
                    target[propertyName] = sourceValue.ToObject<double>();
                }
                catch
                {
                    target[propertyName] = 0.0; // 변환 실패시 기본값
                }
            }
            else
            {
                target[propertyName] = 0.0;
            }
        }

        /// <summary>
        /// 불리언 타입 속성 매핑
        /// </summary>
        private void MapBooleanProperty(string propertyName, JToken sourceValue, DataObject target)
        {
            if (sourceValue.Type == JTokenType.Boolean)
            {
                target[propertyName] = sourceValue.ToObject<bool>();
            }
            else if (sourceValue.Type == JTokenType.Integer)
            {
                // 0은 false, 나머지는 true로 처리
                target[propertyName] = sourceValue.ToObject<int>() != 0;
            }
            else if (sourceValue.Type == JTokenType.String)
            {
                string strValue = sourceValue.ToString().ToLower();
                // 일반적인 참/거짓 문자열 처리
                if (strValue == "true" || strValue == "yes" || strValue == "y" || strValue == "1")
                {
                    target[propertyName] = true;
                }
                else if (strValue == "false" || strValue == "no" || strValue == "n" || strValue == "0")
                {
                    target[propertyName] = false;
                }
                else
                {
                    // 나머지는 bool.Parse 시도
                    try
                    {
                        target[propertyName] = bool.Parse(strValue);
                    }
                    catch
                    {
                        target[propertyName] = false; // 변환 실패시 기본값
                    }
                }
            }
            else
            {
                target[propertyName] = false;
            }
        }

        /// <summary>
        /// DateTime 타입 속성 매핑
        /// </summary>
        private void MapDateTimeProperty(string propertyName, JToken sourceValue, DataObject target)
        {
            if (sourceValue.Type == JTokenType.Date)
            {
                target[propertyName] = sourceValue.ToObject<DateTime>();
            }
            else if (sourceValue.Type == JTokenType.String)
            {
                string dateStr = sourceValue.ToString();
                if (DateTime.TryParse(dateStr, out DateTime dateValue))
                {
                    target[propertyName] = dateValue;
                }
                else
                {
                    target[propertyName] = null;
                }
            }
            else
            {
                target[propertyName] = null;
            }
        }

        /// <summary>
        /// 열거형 타입 속성 매핑
        /// </summary>
        private void MapEnumProperty(string propertyName, JToken sourceValue, object maskValue, DataObject target)
        {
            try
            {
                Type enumType = maskValue.GetType();
                if (sourceValue.Type == JTokenType.String)
                {
                    target[propertyName] = Enum.Parse(enumType, sourceValue.ToString(), true);
                }
                else if (sourceValue.Type == JTokenType.Integer)
                {
                    target[propertyName] = Enum.ToObject(enumType, sourceValue.ToObject<int>());
                }
                else
                {
                    target[propertyName] = maskValue; // 변환 불가능하면 마스크 기본값 사용
                }
            }
            catch
            {
                target[propertyName] = maskValue; // 변환 실패시 마스크 기본값 사용
            }
        }

        /// <summary>
        /// 중첩된 객체 타입 속성 매핑
        /// </summary>
        private void MapNestedObjectProperty(string propertyName, JToken sourceValue, DataMask nestedMask, DataObject target, string path)
        {
            if (sourceValue.Type == JTokenType.Object)
            {
                target[propertyName] = MapObject((JObject)sourceValue, nestedMask, 0, $"{path}.{propertyName}");
            }
            else if (sourceValue.Type == JTokenType.String)
            {
                // 문자열을 JSON으로 파싱 시도
                try
                {
                    JObject jObj = JObject.Parse(sourceValue.ToString());
                    target[propertyName] = MapObject(jObj, nestedMask, 0, $"{path}.{propertyName}");
                }
                catch
                {
                    target[propertyName] = sourceValue.ToString();
                }
            }
            else
            {
                target[propertyName] = ConvertJTokenToObject(sourceValue);
            }
        }

        /// <summary>
        /// 중첩된 배열 타입 속성 매핑
        /// </summary>
        private void MapNestedArrayProperty(string propertyName, JToken sourceValue, List<DataMask> maskTemplates, DataObject target, string path)
        {
            if (sourceValue.Type == JTokenType.Array)
            {
                target[propertyName] = MapArray((JArray)sourceValue, maskTemplates, $"{path}.{propertyName}");
            }
            else if (sourceValue.Type == JTokenType.String)
            {
                // 문자열을 JSON 배열로 파싱 시도
                try
                {
                    JArray jArr = JArray.Parse(sourceValue.ToString());
                    target[propertyName] = MapArray(jArr, maskTemplates, $"{path}.{propertyName}");
                }
                catch
                {
                    // 배열 파싱 실패시 단일 항목 배열로 처리
                    var dataObject = new DataObject();
                    dataObject.Add("value", sourceValue.ToString());
                    var array = new DataArray(1);
                    array.Add(dataObject);
                    target[propertyName] = array;
                }
            }
            else
            {
                // 다른 타입은 단일 항목 배열로 변환
                var dataObject = new DataObject();
                dataObject.Add("value", ConvertJTokenToObject(sourceValue));
                var array = new DataArray(1);
                array.Add(dataObject);
                target[propertyName] = array;
            }
        }

        /// <summary>
        /// DataMap 타입 속성 매핑 (문자열 매핑)
        /// </summary>
        private void MapDataMapProperty(string propertyName, JToken sourceValue, object maskValue, DataObject target)
        {
            Dictionary<string, string> dataMap;

            if (maskValue is DataMap dm)
            {
                dataMap = dm;
            }
            else
            {
                dataMap = (Dictionary<string, string>)maskValue;
            }

            if (sourceValue.Type == JTokenType.String)
            {
                string strValue = sourceValue.ToString();
                if (dataMap.ContainsKey(strValue))
                {
                    target[propertyName] = dataMap[strValue];
                }
                else
                {
                    target[propertyName] = strValue;
                }
            }
            else
            {
                target[propertyName] = sourceValue.ToObject<object>();
            }
        }

        /// <summary>
        /// JToken 타입 속성 매핑 (이전 코드와의 호환성)
        /// </summary>
        private void MapJTokenProperty(string propertyName, JToken sourceValue, JToken jToken, DataObject target)
        {
            if (sourceValue.Type == jToken.Type)
            {
                switch (sourceValue.Type)
                {
                    case JTokenType.String:
                        target[propertyName] = sourceValue.ToObject<string>();
                        return;
                    case JTokenType.Integer:
                        target[propertyName] = sourceValue.ToObject<int>();
                        return;
                    case JTokenType.Float:
                        target[propertyName] = sourceValue.ToObject<double>();
                        return;
                    case JTokenType.Boolean:
                        target[propertyName] = sourceValue.ToObject<bool>();
                        return;
                    case JTokenType.Date:
                        target[propertyName] = sourceValue.ToObject<DateTime>();
                        return;
                }
            }

            if (jToken.Type == JTokenType.Date && sourceValue.Type == JTokenType.String)
            {
                string dateStr = sourceValue.ToObject<string>();
                if (DateTime.TryParse(dateStr, out DateTime dateValue))
                {
                    target[propertyName] = dateValue;
                }
                else
                {
                    target[propertyName] = null;
                }
            }
            else if (jToken.Type == JTokenType.Object && sourceValue.Type == JTokenType.String)
            {
                try
                {
                    // 먼저 DataMap로 변환 시도
                    var dataMap = jToken.ToObject<DataMap>();
                    if (dataMap != null)
                    {
                        string strValue = sourceValue.ToObject<string>();
                        if (dataMap.ContainsKey(strValue))
                        {
                            target[propertyName] = dataMap[strValue];
                        }
                        else
                        {
                            target[propertyName] = strValue;
                        }
                    }
                    else
                    {
                        // DataMap가 아니면 소스 값 그대로 사용
                        target[propertyName] = sourceValue.ToObject<object>();
                    }
                }
                catch
                {
                    // 변환 실패 시 소스 값 그대로 사용
                    target[propertyName] = sourceValue.ToObject<object>();
                }
            }
            else
            {
                // 기타 타입은 그대로 객체로 변환
                target[propertyName] = ConvertJTokenToObject(sourceValue);
            }
        }

        #endregion

    }
}

