Editor/HumanoidRetargeter/Embedded/ValveResourceFormat/Serialization/KeyValues/KVValue.cs
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using HumanoidRetargeterVrf.Utils;
using KVValueType = HumanoidRetargeterKeyValue.KVValueType;

namespace HumanoidRetargeterVrf.Serialization.KeyValues
{
    /// <summary>
    /// Flags for KeyValue values.
    /// </summary>
    public enum KVFlag : byte
    {
#pragma warning disable CS1591
        None = 0,
        Resource = 1,
        ResourceName = 2,
        Panorama = 3,
        SoundEvent = 4,
        SubClass = 5,
        EntityName = 6,

        // There are more types available in the S2 binaries, but they should not be persisted. Look for "The specific type '%s' cannot be persisted"
        MaxPersistedFlag = EntityName,
#pragma warning restore CS1591
    }

    /// <summary>
    /// Structure to hold type + flag + value
    /// </summary>
    public readonly struct KVValue
    {
        /// <summary>
        /// Gets the value type.
        /// </summary>
        public readonly KVValueType Type { get; } = KVValueType.Null;

        /// <summary>
        /// Gets the value flag.
        /// </summary>
        public readonly KVFlag Flag { get; }

        /// <summary>
        /// Gets the value.
        /// </summary>
        public readonly object? Value { get; } = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="KVValue"/> struct.
        /// </summary>
        /// <param name="type">The value type.</param>
        /// <param name="value">The value.</param>
        public KVValue(KVValueType type, object value)
        {
            Type = type;
            Value = value;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="KVValue"/> struct.
        /// </summary>
        /// <param name="type">The value type.</param>
        /// <param name="flag">The value flag.</param>
        /// <param name="value">The value.</param>
        public KVValue(KVValueType type, KVFlag flag, object value)
        {
            Type = type;
            Flag = flag;
            Value = value;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="KVValue"/> struct, inferring the type from the value.
        /// </summary>
        /// <param name="value">The value.</param>
        public KVValue(object? value)
        {
            if (value is KVValue v)
            {
                Type = v.Type;
                Value = v.Value;
                // note: we remove the flag for decompilation purposes
                // we should not be hitting this path when parsing from binary resources
                Flag = KVFlag.None;
            }
            else if (value is global::System.Numerics.Vector3 vec3)
            {
                Type = KVValueType.Array;
                Value = MakeArray([vec3.X, vec3.Y, vec3.Z]).Value;
            }
            else
            {
                Type = value switch
                {
                    string => KVValueType.String,
                    bool => KVValueType.Boolean,
                    int => KVValueType.Int32,
                    uint => KVValueType.UInt32,
                    long => KVValueType.Int64,
                    float => KVValueType.FloatingPoint,
                    double => KVValueType.FloatingPoint64,
                    KVObject kv => kv.IsArray ? KVValueType.Array : KVValueType.Collection,
                    null => KVValueType.Null,
                    _ => throw new NotImplementedException()
                };
                Value = value;
            }
        }

        internal static KVValue MakeArray<T>(IEnumerable<T> values)
        {
            var list = new KVObject(null, isArray: true);
            foreach (var value in values)
            {
                list.AddProperty(null, new KVValue(value));
            }

            return new KVValue(KVValueType.Array, list);
        }
    }
}