#nullable enable

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting.Antlr3.Runtime;

namespace UVC.Data
{
    /// <summary>
    /// JSON 데이터의 구조와 변환 규칙을 정의하는 마스크 클래스입니다.
    /// Dictionary<string, object>를 상속하여 JSON 데이터 구조를 표현하고,
    /// 데이터 매핑, 필드 이름 변환, 그리고 타입 변환을 위한 메타데이터를 제공합니다.
    /// </summary>
    /// <remarks>
    /// DataMask는 주로 DataMapper 클래스와 함께 사용되어 서로 다른 JSON 형식 간의
    /// 데이터 변환 및 매핑 규칙을 정의합니다. 기본 JSON 구조 외에도 추가 속성들을
    /// 통해 매핑 과정에서 필요한 메타데이터를 제공합니다.
    /// </remarks>
    /// <example>
    /// 기본 사용 예시:
    /// <code>
    /// // 마스크 객체 생성 및 설정
    /// var mask = new DataMask();
    /// mask["name"] = "";              // 문자열 타입 지정
    /// mask["age"] = 0;                // 정수 타입 지정
    /// mask["isActive"] = false;       // 불리언 타입 지정
    /// mask["height"] = 0.0;           // 실수 타입 지정
    /// 
    /// // 메타 속성 설정
    /// mask.ObjectIdKey = "name";      // DataObject의 ID로 사용할 필드 지정
    /// mask.ObjectName = "employees";  // 데이터 객체의 이름 지정
    /// 
    /// // 필드 이름 변환 규칙 설정
    /// mask.NamesForReplace = new Dictionary<string, string>
    /// {
    ///     { "full_name", "name" },    // JSON의 full_name을 name으로 변환
    ///     { "employee_age", "age" }   // JSON의 employee_age를 age로 변환
    /// };
    /// 
    /// // 매핑 예시 (DataMapper 클래스와 함께 사용)
    /// var sourceJson = JObject.Parse(@"{
    ///     ""full_name"": ""김철수"",
    ///     ""employee_age"": 30,
    ///     ""isActive"": true,
    ///     ""height"": 175.5
    /// }");
    /// 
    /// var mapper = new DataMapper(mask);
    /// DataObject result = mapper.Mapping(sourceJson);
    /// // result는 변환된 필드 이름과 타입을 가진 DataObject 객체
    /// </code>
    /// </example>
    public class DataMask : Dictionary<string, object>
    {
        /// <summary>
        /// DataObject의 Id에 해당하는 key 문자열입니다.
        /// 이 속성은 매핑된 DataObject에서 고유 식별자로 사용될 필드를 지정합니다.
        /// null인 경우 DataObject의 기본 동작을 따릅니다.
        /// </summary>
        /// <example>
        /// <code>
        /// var mask = new DataMask();
        /// mask.ObjectIdKey = "userId";  // DataObject에서 "userId" 필드가 Id로 사용됨
        /// </code>
        /// </example>
        public string? ObjectIdKey { get; set; } = null;

        /// <summary>
        /// DataObject의 이름을 나타내는 속성입니다. DataRepository에서 사용됩니다.
        /// 이 이름은 매핑된 DataObject를 분류하거나 식별하는 데 사용될 수 있습니다.
        /// </summary>
        /// <example>
        /// <code>
        /// var mask = new DataMask();
        /// mask.ObjectName = "users";  // 매핑된 DataObject는 "users"라는 이름을 가짐
        /// </code>
        /// </example>
        public string ObjectName { get; set; } = string.Empty;

        /// <summary>
        /// 원본 JSON에서 매핑된 DataObject로 필드 이름을 변환하는 규칙을 정의하는 딕셔너리입니다.
        /// 키는 원본 JSON의 필드 이름이고, 값은 변환될 대상 필드 이름입니다.
        /// </summary>
        /// <remarks>
        /// 이 속성을 통해 서로 다른 명명 규칙을 사용하는 JSON 구조 간의 매핑을 용이하게 할 수 있습니다.
        /// 예를 들어, snake_case를 사용하는 API 응답을 camelCase로 변환하는 데 사용할 수 있습니다.
        /// </remarks>
        /// <example>
        /// <code>
        /// var mask = new DataMask();
        /// mask.NamesForReplace = new Dictionary<string, string>
        /// {
        ///     { "first_name", "firstName" },
        ///     { "last_name", "lastName" },
        ///     { "birth_date", "birthDate" }
        /// };
        /// </code>
        /// </example>
        public Dictionary<string, string>? NamesForReplace { get; set; }

        public DataMask() { }

        public DataMask(string jsonString)
        {
            if (string.IsNullOrEmpty(jsonString)) return;
            JObject jObj = JObject.Parse(jsonString);
            foreach (var property in jObj.Properties())
            {
                this[property.Name] = ConvertJTokenToObject(property);
            }
        }

        public DataMask(JObject jObj)
        {
            if (jObj == null) return;
            foreach (var property in jObj.Properties())
            {
                this[property.Name] = ConvertJTokenToObject(property.Value);
            }
        }

        /// <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.Property:
                    JProperty prop = (JProperty)token;
                    return ConvertJTokenToObject(prop.Value);
                case JTokenType.Object:
                    return new DataMask((JObject)token);
                case JTokenType.Array:
                    JArray array = (JArray)token;
                    List<DataMask> dataMasks = new List<DataMask>();
                    if (array.All(item => item.Type == JTokenType.Object))
                    {
                        foreach (var item in array)
                        {
                            dataMasks.Add(new DataMask((JObject)item));
                        }
                    }
                    return dataMasks;
                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>
        /// JObject 타입과의 호환성을 위한 메서드입니다.
        /// Dictionary에 저장된 모든 속성을 JObject 형식으로 반환합니다.
        /// </summary>
        /// <returns>현재 DataMask의 내용을 담은 JObject</returns>
        public JObject ToJObject()
        {
            JObject result = new JObject();
            foreach (var pair in this)
            {
                if (pair.Value is DataMask)
                {
                    result[pair.Key] = ((DataMask)pair.Value).ToJObject();
                }
                else
                {
                    result[pair.Key] = JToken.FromObject(pair.Value);
                }
            }
            return result;
        }

        /// <summary>
        /// JObject에서 속성을 가져오기 위한 확장 메서드입니다.
        /// </summary>
        /// <param name="jObj">속성을 추출할 JObject</param>
        /// <returns>JObject의 모든 속성</returns>
        private IEnumerable<KeyValuePair<string, JToken>> GetProperties(JObject jObj)
        {
            foreach (var property in jObj.Properties())
            {
                yield return new KeyValuePair<string, JToken>(property.Name, property.Value);
            }
        }

        /// <summary>
        /// 다른 DataMask 객체의 속성을 현재 객체로 복사합니다.
        /// </summary>
        /// <param name="other">복사할 원본 DataMask 객체</param>
        /// <param name="maskPropertyOnly">true인 경우 메타데이터 속성만 복사하고 데이터 필드는 복사하지 않음</param>
        /// <remarks>
        /// maskPropertyOnly가 true인 경우 ObjectIdKey, ObjectName, NamesForReplace와 같은
        /// 메타데이터 속성만 복사되고, 실제 데이터 필드(Dictionary의 요소)는 복사되지 않습니다.
        /// false인 경우 모든 속성과 데이터 필드가 복사됩니다.
        /// </remarks>
        /// <example>
        /// <code>
        /// var sourceMask = new DataMask();
        /// sourceMask.ObjectIdKey = "id";
        /// sourceMask["name"] = "";
        /// sourceMask["age"] = 0;
        /// 
        /// var targetMask = new DataMask();
        /// // 모든 속성 복사
        /// targetMask.CopyFrom(sourceMask);
        /// 
        /// // 메타데이터만 복사
        /// var metaOnlyMask = new DataMask();
        /// metaOnlyMask.CopyFrom(sourceMask, true);
        /// </code>
        /// </example>
        public void CopyFrom(DataMask other, bool maskPropertyOnly = false)
        {
            if (other == null) return;
            this.ObjectIdKey = other.ObjectIdKey;
            this.ObjectName = other.ObjectName;
            this.NamesForReplace = other.NamesForReplace != null ? new Dictionary<string, string>(other.NamesForReplace) : null;
            if (maskPropertyOnly) return;
            foreach (var pair in other)
            {
                this[pair.Key] = pair.Value;
            }
        }

        /// <summary>
        /// JObject의 속성을 현재 DataMask 객체로 복사합니다.
        /// </summary>
        /// <param name="other">복사할 원본 JObject</param>
        /// <remarks>
        /// 이 메서드는 JObject의 각 속성을 적절한 .NET 객체로 변환하여
        /// DataMask에 추가합니다. JObject에서 데이터를 가져오는 데 유용합니다.
        /// </remarks>
        /// <example>
        /// <code>
        /// var jsonObject = JObject.Parse(@"{
        ///     ""name"": ""김철수"",
        ///     ""age"": 30,
        ///     ""isActive"": true
        /// }");
        /// 
        /// var mask = new DataMask();
        /// mask.CopyFrom(jsonObject);
        /// // mask에는 jsonObject의 모든 속성이 복사됨
        /// </code>
        /// </example>
        public void CopyFrom(JObject other)
        {
            if (other == null) return;
            foreach (var property in other.Properties())
            {
                this[property.Name] = property.Value.ToObject<object>();
            }
        }

        /// <summary>
        /// 현재 DataMask 객체의 깊은 복사본을 생성합니다.
        /// </summary>
        /// <returns>현재 객체의 모든 값과 메타데이터를 포함한 새로운 DataMask 객체</returns>
        /// <remarks>
        /// 이 메서드는 현재 DataMask의 모든 속성(ObjectIdKey, ObjectName, NamesForReplace)과
        /// 모든 데이터 필드를 새 DataMask 인스턴스에 복사합니다. 복사된 객체는 원본과
        /// 독립적으로 수정할 수 있습니다.
        /// </remarks>
        /// <example>
        /// <code>
        /// var originalMask = new DataMask();
        /// originalMask.ObjectIdKey = "id";
        /// originalMask.ObjectName = "users";
        /// originalMask["name"] = "";
        /// originalMask["age"] = 0;
        /// 
        /// // 깊은 복사 생성
        /// var copiedMask = originalMask.DeepClone();
        /// 
        /// // 원본에 영향 없이 복사본 수정 가능
        /// copiedMask.ObjectName = "employees";
        /// copiedMask["salary"] = 0.0;
        /// </code>
        /// </example>
        public DataMask DeepClone()
        {
            DataMask mask = new DataMask();
            mask.ObjectIdKey = ObjectIdKey;
            mask.ObjectName = ObjectName;
            mask.NamesForReplace = NamesForReplace != null ? new Dictionary<string, string>(NamesForReplace) : null;
            foreach (var pair in this)
            {
                mask[pair.Key] = pair.Value;
            }
            return mask;
        }

        /// <summary>
        /// Json 문자열로 변환합니다.
        /// </summary>
        /// <returns></returns>
        public string ToJsonString()
        {
            return ToJObject().ToString(Newtonsoft.Json.Formatting.Indented);
        }
    }
}