DumpNode.cs

#nullable enable
 
using System;
using System.Linq;
using System.Reflection;
using System.Collections;
using System.Diagnostics;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
 
 
namespace Khooz.Show.Variable
{
    [StructLayout(LayoutKind.Sequential)]
    public class DumpNode
    {
        private const byte MAX_DEPTH = 0xff;
        private const int MAX_VAL_LEN = 12;
        private const string OBS_OBJ = "@{...}";
 
        private static readonly List<string> CACHE_TYPES = [
            "RuntimeType",
            "GenericCache",
            "RuntimeTypeHandle",
            "RuntimeTypeCache",
            "RuntimeTypeInfo",
            "RuntimeTypeInfoCache",
            "System.RuntimeType",
            "System.GenericCache",
            "System.RuntimeTypeHandle",
            "System.RuntimeTypeCache",
            "System.RuntimeTypeInfo",
            "System.RuntimeTypeInfoCache",
        ];
 
        public class Token(string text, ConsoleColor? color)
        {
            public string Text { get; } = text;
            public ConsoleColor Color { get; set; } = color ?? ConsoleColor.White;
 
            // Map ConsoleColor -> ANSI 30–37 codes
            static readonly Dictionary<ConsoleColor, int> _map = new()
            {
                [ConsoleColor.Black] = 30,
                [ConsoleColor.Red] = 31,
                [ConsoleColor.Green] = 32,
                [ConsoleColor.Yellow] = 33,
                [ConsoleColor.Blue] = 34,
                [ConsoleColor.Magenta] = 35,
                [ConsoleColor.Cyan] = 36,
                [ConsoleColor.White] = 37,
                // bright variants if desired: 90–97
            };
 
            public Token(string Text) : this(Text, null) { }
            public override string ToString()
            {
                 
                int code = _map.ContainsKey(Color) ? _map[Color] : 0;
                return $"\u001b[{code}m{Text}\u001b[0m";
            }
 
            public static void Write(Token t)
            {
                Console.ForegroundColor = t.Color;
                Console.Write(t.Text);
                Console.ResetColor();
            }
}
 
        [StructLayout(LayoutKind.Sequential)]
        public class Representation
        {
            public enum Category
            {
                _ = 0x00,
                PRIMITIVE = 0x01,
                BOOLEAN = 0x03,
                NUMBER = 0x05,
                STRING = 0x09,
                OBJECT = 0x10,
                ENUMERABLE = 0x30,
                COLLECTION = 0x70,
                DICTIONARY = 0xf0,
                FUNCTION = 0x100
            };
 
            public enum Accessor
            {
                _ = 0x00,
                PUBLIC = 0x01,
                PROTECTED = 0X02,
                PRIVATE = 0X04,
                INTERNAL = 0X08,
                PROTECTEDINTERNAL = 0X10,
                PRIVATEPROTECTED = 0X20,
                GETTER = 0x40,
                SETTER = 0x80,
 
 
            };
 
            public enum Modifier
            {
                _ = 0X00,
                READONLY = 0X01,
                STATIC = 0X02,
                VIRTUAL = 0X04,
                ABSTRACT = 0X08,
                SEALED = 0X10,
                OVERRIDE = 0X20,
                ASYNC = 0X40,
                VOLATILE = 0X80
            }
 
            public byte Accessors { get; set; } = 0;
            public byte Modifiers { get; set; } = 0;
            public string TypeName { get; set; } = "null";
            public Category Cat { get; set; } = Category._;
            public string Name { get; set; } = "";
            public string Value { get; set; } = "";
 
            public Category GetCategory()
            {
                return TypeName switch
                {
                    "System.Boolean" or "Boolean" => Category.BOOLEAN,
                    "System.Byte" or "Byte"
                        or "System.SByte" or "SByte"
                        or "System.Char" or "Char"
                        or "System.Int16" or "Int16"
                        or "System.UInt16" or "UInt16"
                        or "System.Int32" or "Int32"
                        or "System.UInt32" or "UInt32"
                        or "System.Int64" or "Int64"
                        or "System.UInt64" or "UInt64"
                        or "System.IntPtr" or "IntPtr"
                        or "System.UIntPtr" or "UIntPtr"
                        or "System.Single" or "Single"
                        or "System.Double" or "Double"
                        or "System.Decimal" or "Decimal" => Category.NUMBER,
                    "String" or "String" => Category.STRING,
                    _ when TypeName.StartsWith("IEnumerable") => Category.ENUMERABLE,
                    _ when TypeName.StartsWith("IDictionary") => Category.DICTIONARY,
                    _ when TypeName.StartsWith("Func") || TypeName.StartsWith("Action") => Category.FUNCTION,
                    _ => Category.OBJECT
                };
            }
            public override string ToString()
            {
                return ToString(false);
            }
            public string ToString(bool colorize)
            {
                var tn = TypeName;
                var n = Name;
                var v = Value;
                // var tn = TypeName;
                if (colorize)
                {
                    tn = $"\u001b[34m{tn}\u001b[0m";
                    v = GetCategory() switch
                    {
                        Category.BOOLEAN => $"{TypeName} {Name} {Value}",
                        Category.NUMBER => $"{TypeName} {Name} {Value}",
                        Category.STRING => $"\u001b[31m\"{FormatValue(Value)}\u001b[0m\"",
                        Category.ENUMERABLE or Category.DICTIONARY => Value,
                        Category.FUNCTION => $"Func<{Value}>",
                        _ => $"{TypeName} {Name} {FormatValue(Value)}",
                    };
                }
                return GetCategory() switch
                {
                    Category.BOOLEAN => $"{tn} {n} {v}",
                    Category.NUMBER => $"{tn} {n} {v}",
                    Category.STRING => $"{tn} {n} {v}",
                    Category.ENUMERABLE or Category.DICTIONARY => $"{tn} {n} {v}",
                    Category.FUNCTION => $"v",
                    _ => $"{tn} {n} {v}",
                };
            }
 
            public List<Token> ToTokens(bool colorize)
            {
                Token tn = new(TypeName);
                Token n = new(Name);
                Token v = new(Value);
                // var tn = TypeName;
                if (colorize)
                {
                    tn.Color = ConsoleColor.Blue;
                    v.Color = GetCategory() switch
                    {
                        Category._ or Category.BOOLEAN=> ConsoleColor.Blue,
                        Category.NUMBER => ConsoleColor.Green,
                        Category.STRING => v.Text == "null" ? ConsoleColor.Blue : ConsoleColor.DarkYellow,
                        Category.ENUMERABLE or Category.DICTIONARY => ConsoleColor.Gray,
                        Category.FUNCTION => ConsoleColor.DarkMagenta,
                        _ => v.Color,
                    };
                }
                return [tn, n, v];
            }
 
            public override int GetHashCode()
            {
                return HashCode.Combine(Name, TypeName, Value);
            }
 
            public bool ReplaceInfo(Accessor access = Accessor._,Modifier mods = Modifier._)
            {
                Accessors = (byte)access;
                Modifiers = (byte)mods;
                return true;
            }
            public bool AddInfo(Accessor access = Accessor._,Modifier mods = Modifier._)
            {
                Accessors = (byte)(Accessors | (byte)access);
                Modifiers = (byte)(Modifiers | (byte)mods);
                return true;
            }
            public bool RemoveInfo(Accessor access = Accessor._,Modifier mods = Modifier._)
            {
                Accessors = (byte)(Accessors & ~(byte)access);
                Modifiers = (byte)(Modifiers & ~(byte)mods);
                return true;
            }
 
 
        }
 
        private readonly byte depth;
        // public object? Value { get; set; }
        public Representation Repr { get; }
        public List<DumpNode> Children { get; }
 
         
        public DumpNode(object? val) : this(val, null, 0, null) { }
        public DumpNode(object? val, Representation? repr) : this(val, repr, 0, null) { }
        public DumpNode(object? val, Representation? repr, byte depth) : this(val, repr, depth, null) { }
        public DumpNode(object? val, Representation? representation, byte depth, byte? maxDepth = null)
        {
            this.Repr = representation ?? new()
            {
                Name = "$ROOT",
                TypeName = val is null ? "null" :
                    val.GetType().Name
            };
            this.depth = depth;
            maxDepth ??= MAX_DEPTH;
            Children = [];
            // Console.WriteLine(Repr.ToString());
// return;
// }
// /*
            if (val?.GetType() == typeof(object)) return;
            if (
                depth < maxDepth
                && val is not string
                && (!val?.GetType()?.IsPrimitive ?? false)
            )
            {
                // Handle arrays and collections
                if (val is IEnumerable enumerable && val is not IDictionary)
                    {
                        var items = new List<object?>();
                        int idx = 0;
                        foreach (var item in enumerable)
                        {
                            Representation i_repr = new()
                            {
                                Name = $"[{idx}]",
                                TypeName = item?.GetType().Name ?? "null",
                            };
                            Children.Add(new DumpNode(item, i_repr, (byte)(depth + 1), maxDepth));
                            items.Add(item);
                            idx++;
                            if (idx >= 30) break; // limit preview
                        }
                        Repr.Value = $"[{idx} items]";
                    }
                // Handle dictionaries
                else if (val is IDictionary dict)
                {
                    var args = val?.GetType().GetGenericArguments() ?? [];
                    Repr.TypeName = val?.GetType().Name ?? "Dictionary";
                    var tick = Repr.TypeName.IndexOf('`');
                    if (tick > 0) Repr.TypeName = Repr.TypeName[..tick];
                    Repr.TypeName = args.Length > 0
                        ? $"{Repr.TypeName}<{args?[0].Name ?? "K"},{args?[1].Name ?? "V"}>"
                        : $"{Repr.TypeName}<Object?,Object?>";
 
                    int idx = 0;
                    foreach (DictionaryEntry entry in dict)
                    {
                        Representation i_repr = new()
                        {
                            Name = args?.Length > 0 ? $"[{entry.Key}]" : $"[{entry.Key}.{entry.Key.GetHashCode()}]",
                            TypeName = entry.Value?.GetType().Name ?? "null",
                        };
                        Children.Add(new DumpNode(entry.Value, i_repr, (byte)(depth + 1), maxDepth));
                        idx++;
                        if (idx >= 30) break;
                    }
                    Repr.Value = $"{{{idx} pairs}}";
                }
                else
                {
                    Repr.Value = "@{}";
                    Repr.TypeName = Repr.TypeName.Contains("AnonymousType")
                        ? "AnonymousType"
                        : Repr.TypeName;
 
                    // Collect props, fields and methods for matching
                    var props = val?.GetType()
                            ?.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                            ?.ToDictionary(f => f.Name) ?? [];
 
                    var fields = val?.GetType()
                        ?.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                        ?.ToDictionary(f => PropertyName(f.Name)) ?? [];
 
                    var methods = val?.GetType()
                            ?.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                            ?? [];
 
 
                    // Handle properties, merge with backing fields
                    foreach (var prop in props.Values)
                    {
                        if (
                            (prop.DeclaringType != val?.GetType())
                            // && CACHE_TYPES.Contains(prop.GetType().Name)
                            && IsSystemType(prop.GetType())
                        ) continue;
                        // if (prop.GetIndexParameters().Length > 0) continue; // skip indexed properties
                        // Try to find backing field
                        string backingFieldName = fields.Keys.SingleOrDefault(
                                _ => _.StartsWith($"<{prop.Name}>", StringComparison.OrdinalIgnoreCase),
                                ""
                            );
                        object childVal;
                        try { childVal = prop.GetValue(val, null)!; }
                        catch { continue; }
 
 
 
                        // Build Repr
                        var propType = prop.PropertyType.Name;
                        var propName = prop.Name + (backingFieldName.Length > 0 ? $"({backingFieldName})" : "");
 
                        Representation i_repr = new()
                        {
                            Name = $"{prop.Name}",
                            TypeName = prop.PropertyType.Name,
                            Value = childVal is null ? "null" : childVal.ToString() ?? OBS_OBJ,
                        };
 
                        // Describe accessors
                        SetPropAttr(i_repr: ref i_repr, val: prop);
 
                        Children.Add(
                            new DumpNode(childVal, i_repr, (byte)(depth + 1), maxDepth));
                    }
                    // Add fields not used as backing fields
                    foreach (
                        var field
                        in fields
                            .Where(kv => !props.ContainsKey(kv.Key) || !kv.Value.Name.StartsWith($"<{props[kv.Key].Name}>"))
                            .ToDictionary().Values
                    )
                    {
                        if (
                            (field.DeclaringType != val?.GetType())
                            // && CACHE_TYPES.Contains(field.GetType().Name)
                            || IsSystemType(field)
                        ) continue;
                        object fieldVal;
                        try { fieldVal = field.GetValue(val)!; } catch { continue; }
 
                        Representation i_repr = new()
                        {
                            Name = $"{field.Name}",
                            TypeName = field.FieldType.Name,
                            Value = fieldVal is null ? "null" : fieldVal.ToString() ?? OBS_OBJ
                        };
 
                        SetFieldAttr(i_repr: ref i_repr, val: field);
 
                        Children.Add(
                            new DumpNode(fieldVal, i_repr, (byte)(depth + 1), maxDepth));
                    }
 
                    // Add method signatures (bound instance methods only)
                    foreach (var method in methods)
                    {
                        if (
                            method.DeclaringType != val?.GetType()
                            || IsSystemType(method.GetType())
                            || (
                                method.DeclaringType == typeof(object)
                                && method.GetBaseDefinition() == method
                            )
                        ) continue;
                        if (method.IsSpecialName) continue; // skip property accessors, etc.
 
 
                        // Build method signature string
                        var parameters = method.GetParameters();
                        var paramList = parameters.Length > 0
                            ? string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"))
                            : "";
 
                        var signature = $"{method.ReturnType.Name} {method.Name}({paramList})";
                        Representation i_repr = new()
                        {
                            Name = $"{method.Name}",
                            TypeName = $"Func<{method.ReturnType.Name} $RETURN{(paramList.Length > 0 ? $", {paramList}" : "")}>",
                            Value = paramList,
                        };
 
                        SetMethodAttr(i_repr: ref i_repr, val: method);
 
                        Children.Add(
                            new DumpNode(method, i_repr, (byte)(depth + 1), maxDepth));
                    }
                }
            }
            else
            {
                Repr.Value = val switch
                {
                    null => "null",
                    string s => s,
                    bool b => b.ToString().ToLower(),
                    char c => $"'{c}'",
                    _ when val.GetType().IsPrimitive => val.ToString() ?? OBS_OBJ,
                    _ => val.ToString() ?? OBS_OBJ
                };
            }
        }
// */
        private static bool SetPropAttr(ref Representation i_repr, PropertyInfo val)
        {
            // Accessor detection
            var getMethod = val.GetGetMethod(true);
            var setMethod = val.GetSetMethod(true);
            bool is_set = false;
 
            is_set = getMethod != null && i_repr.AddInfo(access: Representation.Accessor.GETTER);
            is_set = getMethod != null && getMethod.IsPublic && i_repr.AddInfo(access: Representation.Accessor.PUBLIC);
            is_set = getMethod != null && getMethod.IsFamily && i_repr.AddInfo(access: Representation.Accessor.PROTECTED);
            is_set = getMethod != null && getMethod.IsPrivate && i_repr.AddInfo(access: Representation.Accessor.PRIVATE);
            is_set = getMethod != null && getMethod.IsAssembly && i_repr.AddInfo(access: Representation.Accessor.INTERNAL);
            is_set = getMethod != null && getMethod.IsFamilyOrAssembly && i_repr.AddInfo(access: Representation.Accessor.PROTECTEDINTERNAL);
            is_set = getMethod != null && getMethod.IsFamilyAndAssembly && i_repr.AddInfo(access: Representation.Accessor.PRIVATEPROTECTED);
 
            is_set = val.CanRead && (getMethod != null) && getMethod.IsStatic && i_repr.AddInfo(mods: Representation.Modifier.STATIC);
            is_set = val.CanRead && (getMethod != null) && getMethod.IsVirtual && i_repr.AddInfo(mods: Representation.Modifier.VIRTUAL);
            is_set = val.CanRead && (getMethod != null) && getMethod.IsAbstract && i_repr.AddInfo(mods: Representation.Modifier.ABSTRACT);
            is_set = val.CanRead && (getMethod != null) && getMethod.IsFinal && i_repr.AddInfo(mods: Representation.Modifier.SEALED);
 
 
            i_repr.AddInfo(access: Representation.Accessor.SETTER);
            is_set = setMethod != null && setMethod.IsPublic && i_repr.AddInfo(access: Representation.Accessor.PUBLIC);
            is_set = setMethod != null && setMethod.IsFamily && i_repr.AddInfo(access: Representation.Accessor.PROTECTED);
            is_set = setMethod != null && setMethod.IsPrivate && i_repr.AddInfo(access: Representation.Accessor.PRIVATE);
            is_set = setMethod != null && setMethod.IsAssembly && i_repr.AddInfo(access: Representation.Accessor.INTERNAL);
            is_set = setMethod != null && setMethod.IsFamilyOrAssembly &&
                i_repr.AddInfo(access: Representation.Accessor.PROTECTEDINTERNAL);
            is_set = setMethod != null && setMethod.IsFamilyAndAssembly &&
                i_repr.AddInfo(access: Representation.Accessor.PRIVATEPROTECTED);
 
            is_set = val.CanWrite && setMethod != null && setMethod.IsStatic && i_repr.AddInfo(mods: Representation.Modifier.STATIC);
            is_set = val.CanWrite && setMethod != null && setMethod.IsVirtual && i_repr.AddInfo(mods: Representation.Modifier.VIRTUAL);
            is_set = val.CanWrite && setMethod != null && setMethod.IsAbstract && i_repr.AddInfo(mods: Representation.Modifier.ABSTRACT);
            is_set = val.CanWrite && setMethod != null && setMethod.IsFinal && i_repr.AddInfo(mods: Representation.Modifier.SEALED);
 
 
            // Readonly detection (auto-property with getter only)
            is_set = val.CanRead && !val.CanWrite && i_repr.AddInfo(mods: Representation.Modifier.READONLY);
 
            return is_set;
        }
 
        private static bool SetFieldAttr(ref Representation i_repr, FieldInfo val)
        {
            // Accessor detection
            bool is_set = false;
 
            i_repr.AddInfo(access: Representation.Accessor.GETTER);
            is_set = val.IsPublic && i_repr.AddInfo(access: Representation.Accessor.PUBLIC);
            is_set = val.IsFamily && i_repr.AddInfo(access: Representation.Accessor.PROTECTED);
            is_set = val.IsPrivate && i_repr.AddInfo(access: Representation.Accessor.PRIVATE);
            is_set = val.IsAssembly && i_repr.AddInfo(access: Representation.Accessor.INTERNAL);
            is_set = val.IsFamilyOrAssembly && i_repr.AddInfo(access: Representation.Accessor.PROTECTEDINTERNAL);
            is_set = val.IsFamilyAndAssembly && i_repr.AddInfo(access: Representation.Accessor.PRIVATEPROTECTED);
 
            // readonly detection (auto-property with getter only)
            is_set = val.IsInitOnly && i_repr.AddInfo(mods: Representation.Modifier.READONLY);
            is_set = val.IsLiteral && i_repr.AddInfo(mods: Representation.Modifier.READONLY);
            // static detection
            is_set = val.IsStatic && i_repr.AddInfo(mods: Representation.Modifier.STATIC);
 
            return is_set;
        }
 
        private static bool SetMethodAttr(ref Representation i_repr, MethodInfo val)
        {
            // Accessor detection
            bool is_set = false;
 
            i_repr.AddInfo(access: Representation.Accessor.GETTER);
            is_set = val.IsPublic && i_repr.AddInfo(access: Representation.Accessor.PUBLIC);
            is_set = val.IsFamily && i_repr.AddInfo(access: Representation.Accessor.PROTECTED);
            is_set = val.IsPrivate && i_repr.AddInfo(access: Representation.Accessor.PRIVATE);
            is_set = val.IsAssembly && i_repr.AddInfo(access: Representation.Accessor.INTERNAL);
            is_set = val.IsFamilyOrAssembly && i_repr.AddInfo(access: Representation.Accessor.PROTECTEDINTERNAL);
            is_set = val.IsFamilyAndAssembly && i_repr.AddInfo(access: Representation.Accessor.PRIVATEPROTECTED);
 
            // readonly detection (auto-property with getter only)
            is_set = val.IsStatic && i_repr.AddInfo(mods: Representation.Modifier.STATIC);
            is_set = val.IsVirtual && i_repr.AddInfo(mods: Representation.Modifier.VIRTUAL);
            is_set = val.IsAbstract && i_repr.AddInfo(mods: Representation.Modifier.ABSTRACT);
            is_set = val.IsFinal && i_repr.AddInfo(mods: Representation.Modifier.SEALED);
 
            return is_set;
        }
 
        private static string PropertyName(string backingFieldName)
        {
            Match match = new Regex("<(.*?)>.*").Match(backingFieldName);
            return match.Success ? match.Groups[1].Value : backingFieldName;
        }
 
        private static bool IsSystemType(MemberInfo member)
        {
            return member.DeclaringType == typeof(object)
            || member.GetType().Name.StartsWith("System.", StringComparison.OrdinalIgnoreCase)
            || member.GetType().Name.StartsWith("Runtime", StringComparison.OrdinalIgnoreCase)
            || member.GetType().Name.StartsWith("Generic", StringComparison.OrdinalIgnoreCase);
        }
 
        private static string FormatValue(object? val)
        {
            if (val is null) return "null";
 
            // Strings: quote + truncate
            if (val is string s)
            {
                var quoted = $"\"{s}\"";
                if (quoted.Length <= MAX_VAL_LEN) return quoted;
                return $"\"{s[..(MAX_VAL_LEN - 3)]}...\"";
            }
 
            // Booleans & chars
            if (val is bool b) return b.ToString().ToLower();
            if (val is char c) return $"'{c}'";
 
            // Numeric types: use general or scientific, then truncate
            if (val is byte || val is short || val is int ||
                val is long || val is float || val is double ||
                val is decimal)
            {
                double d = Convert.ToDouble(val);
                string str = d.ToString("G6");
                if (str.Length > MAX_VAL_LEN)
                    str = d.ToString("E4");
                if (str.Length > MAX_VAL_LEN)
                    str = $"{str[..(MAX_VAL_LEN - 3)]}...";
                return str;
            }
 
            // Fallback to .ToString() + truncate
            var txt = val.ToString() ?? "";
            if (txt.Length <= MAX_VAL_LEN) return txt;
            return $"{txt[..(MAX_VAL_LEN - 3)]}...";
        }
 
        public void PrintTree() => PrintTree(true, "", true);
        public void PrintTree(bool color = true) => PrintTree(color, "", true);
        public void PrintTree(bool color = true, string indent = "") => PrintTree(color, indent, true);
        private void PrintTree(bool color = true, string indent = "", bool last = true)
        {
            var branch = last ? "└── " : "├── ";
            // use ValueType.Name or FullName here
            // var typeLabel = Value?.GetType()?.Name ?? "null";
 
            Console.Write($"{indent}{branch}");
            // foreach (var t in Repr.ToTokens(true)) t.Write();
            Repr.ToTokens(true).ForEach(_ => { Console.Write(" "); Token.Write(_); });
            Console.WriteLine();
 
            var childIndent = indent + (last ? " " : "│ ");
            if (Children.Count > 0) for (int i = 0; i < Children.Count; i++)
                Children[i].PrintTree(color, childIndent, i == Children.Count - 1);
        }
    }
}