using System.Collections;
using System.Collections.Concurrent;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using static AyCode.Core.Helpers.JsonUtilities;

namespace AyCode.Core.Serializers.Toons;

/// <summary>
/// High-performance Toon (Token-Oriented Object Notation) serializer optimized for LLM readability.
/// Features:
/// - Human-readable indented format
/// - Separate @meta/@types and @data sections for token efficiency
/// - Type annotations and descriptions for better LLM understanding
/// - Reference handling for circular/shared references
/// - Optimized for Claude and other LLMs to easily parse and understand data structures
/// </summary>
public static partial class AcToonSerializer
{
    /// <summary>
    /// Format version for Toon serialization.
    /// Incremented when breaking changes are made to format.
    /// </summary>
    public const string FormatVersion = "1.0";

    #region Public API

    /// <summary>
    /// Serialize object to Toon format with default options.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static string Serialize<T>(T value) => Serialize(value, AcToonSerializerOptions.Default);

    /// <summary>
    /// Serialize object to Toon format with specified options.
    /// </summary>
    public static string Serialize<T>(T value, AcToonSerializerOptions options) => Serialize<T>(value, string.Empty, options);
    public static string Serialize<T>(T value, string domainDescription, AcToonSerializerOptions options)
    {
        if (value == null) return "null";

        var type = value.GetType();

        // Handle primitive types directly without context
        if (TrySerializePrimitiveDirect(value, type, out var primitiveResult))
            return primitiveResult;

        var context = ToonSerializationContextPool.Get(options);
        try
        {
            // Reference scanning if needed
            if (context.ReferenceHandling != ReferenceHandlingMode.None && !IsPrimitiveOrStringFast(type))
            {
                ScanReferences(value, context, 0);
            }

            // Serialize based on mode
            switch (options.Mode)
            {
                case ToonSerializationMode.MetaOnly:
                    WriteMetaSectionOnly(type, context, domainDescription);
                    break;

                case ToonSerializationMode.DataOnly:
                    WriteDataSectionOnly(value, type, context);
                    break;

                case ToonSerializationMode.Full:
                default:
                    WriteMetaSection(type, context, domainDescription);
                    context.WriteLine();
                    WriteDataSection(value, type, context);
                    break;
            }

            return context.GetResult();
        }
        finally
        {
            ToonSerializationContextPool.Return(context);
        }
    }

    /// <summary>
    /// Serialize only type metadata (schema) for a given type.
    /// Useful for sending type information once at conversation start.
    /// </summary>
    public static string SerializeTypeMetadata<T>() => SerializeTypeMetadata(typeof(T), string.Empty);
    public static string SerializeTypeMetadata<T>(string domainDescription) => SerializeTypeMetadata(typeof(T), domainDescription);

    /// <summary>
    /// Serialize only type metadata (schema) for a given type.
    /// </summary>
    public static string SerializeTypeMetadata(Type type) => SerializeTypeMetadata(type, string.Empty);
    public static string SerializeTypeMetadata(Type type, string domainDescription)
    {
        var context = ToonSerializationContextPool.Get(AcToonSerializerOptions.MetaOnly);
        try
        {
            WriteMetaSectionOnly(type, context, domainDescription);
            return context.GetResult();
        }
        finally
        {
            ToonSerializationContextPool.Return(context);
        }
    }

    /// <summary>
    /// Serialize metadata only for a collection of types (no data instances needed).
    /// Useful for documenting multiple types at once, or when you only have Type objects.
    /// </summary>
    /// <param name="types">Types to document</param>
    /// <param name="options">Serialization options (optional, defaults to MetaOnly preset)</param>
    /// <returns>Metadata-only Toon format string with @meta and @types sections</returns>
    public static string SerializeMetadata(IEnumerable<Type> types, AcToonSerializerOptions? options = null) => SerializeMetadata(types, string.Empty, options);
    public static string SerializeMetadata(IEnumerable<Type> types, string domainDescription, AcToonSerializerOptions? options = null)
    {
        // Return empty string if no types provided
        var typesList = types?.ToList();
        if (typesList == null || typesList.Count == 0)
        {
            return string.Empty;
        }

        options ??= AcToonSerializerOptions.MetaOnly;

        var context = ToonSerializationContextPool.Get(options);
        try
        {
            WriteMetaSection(typesList, context, domainDescription);
            return context.GetResult();
        }
        finally
        {
            ToonSerializationContextPool.Return(context);
        }
    }

    /// <summary>
    /// Serialize metadata only for multiple types (params array overload).
    /// </summary>
    /// <param name="types">Types to document</param>
    /// <returns>Metadata-only Toon format string with @meta and @types sections</returns>
    public static string SerializeMetadata(params Type[] types) => SerializeMetadata((IEnumerable<Type>)types);
    public static string SerializeMetadata(string domainDescription, params Type[] types) => SerializeMetadata((IEnumerable<Type>)types, domainDescription);
    #endregion

    #region Primitive Serialization

    /// <summary>
    /// Fast path for primitive types that don't need context.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static bool TrySerializePrimitiveDirect(object value, Type type, out string result)
    {
        var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
        var typeCode = Type.GetTypeCode(underlyingType);

        switch (typeCode)
        {
            case TypeCode.String:
                result = EscapeString((string)value);
                return true;
            case TypeCode.Int32:
                result = ((int)value).ToString(CultureInfo.InvariantCulture);
                return true;
            case TypeCode.Int64:
                result = ((long)value).ToString(CultureInfo.InvariantCulture);
                return true;
            case TypeCode.Boolean:
                result = (bool)value ? "true" : "false";
                return true;
            case TypeCode.Double:
                var d = (double)value;
                result = double.IsNaN(d) || double.IsInfinity(d) ? "null" : d.ToString("G17", CultureInfo.InvariantCulture);
                return true;
            case TypeCode.Decimal:
                result = ((decimal)value).ToString(CultureInfo.InvariantCulture);
                return true;
            case TypeCode.Single:
                var f = (float)value;
                result = float.IsNaN(f) || float.IsInfinity(f) ? "null" : f.ToString("G9", CultureInfo.InvariantCulture);
                return true;
            case TypeCode.DateTime:
                result = $"\"{((DateTime)value).ToString("O", CultureInfo.InvariantCulture)}\"";
                return true;
            case TypeCode.Byte:
                result = ((byte)value).ToString(CultureInfo.InvariantCulture);
                return true;
            case TypeCode.Int16:
                result = ((short)value).ToString(CultureInfo.InvariantCulture);
                return true;
            case TypeCode.UInt16:
                result = ((ushort)value).ToString(CultureInfo.InvariantCulture);
                return true;
            case TypeCode.UInt32:
                result = ((uint)value).ToString(CultureInfo.InvariantCulture);
                return true;
            case TypeCode.UInt64:
                result = ((ulong)value).ToString(CultureInfo.InvariantCulture);
                return true;
            case TypeCode.SByte:
                result = ((sbyte)value).ToString(CultureInfo.InvariantCulture);
                return true;
            case TypeCode.Char:
                result = EscapeString(value.ToString()!);
                return true;
        }

        if (ReferenceEquals(underlyingType, GuidType))
        {
            result = $"\"{((Guid)value).ToString("D")}\"";
            return true;
        }
        if (ReferenceEquals(underlyingType, DateTimeOffsetType))
        {
            result = $"\"{((DateTimeOffset)value).ToString("O", CultureInfo.InvariantCulture)}\"";
            return true;
        }
        if (ReferenceEquals(underlyingType, TimeSpanType))
        {
            result = $"\"{((TimeSpan)value).ToString("c", CultureInfo.InvariantCulture)}\"";
            return true;
        }
        if (underlyingType.IsEnum)
        {
            result = Convert.ToInt32(value).ToString(CultureInfo.InvariantCulture);
            return true;
        }

        result = "";
        return false;
    }

    /// <summary>
    /// Escape string for Toon format (double quotes, newlines, etc).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static string EscapeString(string value)
    {
        if (string.IsNullOrEmpty(value)) return "\"\"";

        // Check if escaping is needed
        var needsEscaping = false;
        foreach (var c in value)
        {
            if (c == '"' || c == '\\' || c == '\n' || c == '\r' || c == '\t' || c < 32)
            {
                needsEscaping = true;
                break;
            }
        }

        if (!needsEscaping)
            return $"\"{value}\"";

        var sb = new StringBuilder(value.Length + 8);
        sb.Append('"');
        foreach (var c in value)
        {
            switch (c)
            {
                case '"': sb.Append("\\\""); break;
                case '\\': sb.Append("\\\\"); break;
                case '\n': sb.Append("\\n"); break;
                case '\r': sb.Append("\\r"); break;
                case '\t': sb.Append("\\t"); break;
                default:
                    if (c < 32)
                        sb.Append($"\\u{((int)c):x4}");
                    else
                        sb.Append(c);
                    break;
            }
        }
        sb.Append('"');
        return sb.ToString();
    }

    #endregion

    #region Reference Scanning

    private static void ScanReferences(object? value, ToonSerializationContext context, int depth)
    {
        if (value == null) return;

        var type = value.GetType();
        if (IsPrimitiveOrStringFast(type)) return;
        if (!context.TrackForScanning(value)) return;

        if (value is IDictionary dictionary)
        {
            foreach (DictionaryEntry entry in dictionary)
            {
                if (entry.Key != null) ScanReferences(entry.Key, context, depth + 1);
                if (entry.Value != null) ScanReferences(entry.Value, context, depth + 1);
            }
            return;
        }

        if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
        {
            foreach (var item in enumerable)
            {
                if (item != null) ScanReferences(item, context, depth + 1);
            }
            return;
        }

        var metadata = GetTypeMetadata(type);
        foreach (var prop in metadata.Properties)
        {
            var propValue = prop.GetValue(value);
            if (propValue != null) ScanReferences(propValue, context, depth + 1);
        }
    }

    #endregion

    #region Type Metadata

    // Temporary: own cache for static methods without context
    private static readonly ConcurrentDictionary<Type, ToonSerializeTypeMetadata> MetadataCache = new();

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static ToonSerializeTypeMetadata GetTypeMetadata(Type type)
        => MetadataCache.GetOrAdd(type, static t => new ToonSerializeTypeMetadata(t));

    #endregion
}
