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

namespace AyCode.Core.Serializers.Toons;

public static partial class AcToonSerializer
{
    /// <summary>
    /// Write data section only (for DataOnly mode).
    /// </summary>
    private static void WriteDataSectionOnly(object value, Type type, ToonSerializationContext context)
    {
        WriteDataSection(value, type, context);
    }

    /// <summary>
    /// Write @data section.
    /// </summary>
    private static void WriteDataSection(object value, Type type, ToonSerializationContext context)
    {
        context.WriteLine("@data {");
        context.CurrentIndentLevel++;

        WriteValue(value, type, context);
        context.WriteLine();

        context.CurrentIndentLevel--;
        context.WriteLine("}");
    }

    /// <summary>
    /// Write a value (dispatcher for different types).
    /// </summary>
    private static void WriteValue(object? value, Type type, ToonSerializationContext context)
    {
        if (value == null)
        {
            context.Write("null");
            return;
        }

        // Try primitive first
        if (TryWritePrimitive(value, type, context))
            return;

        // Check for reference
        if (context.ReferenceHandling != ReferenceHandlingMode.None && context.TryGetExistingRef(value, out var refId))
        {
            context.Write($"@ref:{refId}");
            return;
        }

        // Handle dictionaries
        if (value is IDictionary dictionary)
        {
            WriteDictionary(dictionary, context);
            return;
        }

        // Handle collections/arrays
        if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
        {
            WriteArray(enumerable, type, context);
            return;
        }

        // Handle complex objects
        WriteObject(value, type, context);
    }

    /// <summary>
    /// Write primitive value inline.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static bool TryWritePrimitive(object value, Type type, ToonSerializationContext context)
    {
        var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
        var typeCode = Type.GetTypeCode(underlyingType);

        string valueStr;
        string? typeHint = null;

        switch (typeCode)
        {
            case TypeCode.String:
                var strValue = (string)value;
                // Check if multi-line string format should be used
                if (context.Options.UseMultiLineStrings &&
                    strValue.Length > context.Options.MultiLineStringThreshold)
                {
                    context.Write(FormatMultiLineString(strValue, context));
                    return true;
                }
                valueStr = EscapeString(strValue);
                typeHint = "string";
                break;
            case TypeCode.Int32:
                valueStr = ((int)value).ToString(CultureInfo.InvariantCulture);
                typeHint = "int32";
                break;
            case TypeCode.Int64:
                valueStr = ((long)value).ToString(CultureInfo.InvariantCulture);
                typeHint = "int64";
                break;
            case TypeCode.Boolean:
                valueStr = (bool)value ? "true" : "false";
                typeHint = "bool";
                break;
            case TypeCode.Double:
                var d = (double)value;
                if (double.IsNaN(d) || double.IsInfinity(d))
                {
                    valueStr = "null";
                }
                else
                {
                    valueStr = d.ToString("G17", CultureInfo.InvariantCulture);
                    typeHint = "float64";
                }
                break;
            case TypeCode.Decimal:
                valueStr = ((decimal)value).ToString(CultureInfo.InvariantCulture);
                typeHint = "decimal";
                break;
            case TypeCode.Single:
                var f = (float)value;
                if (float.IsNaN(f) || float.IsInfinity(f))
                {
                    valueStr = "null";
                }
                else
                {
                    valueStr = f.ToString("G9", CultureInfo.InvariantCulture);
                    typeHint = "float32";
                }
                break;
            case TypeCode.DateTime:
                valueStr = $"\"{((DateTime)value).ToString("O", CultureInfo.InvariantCulture)}\"";
                typeHint = "datetime";
                break;
            case TypeCode.Byte:
                valueStr = ((byte)value).ToString(CultureInfo.InvariantCulture);
                typeHint = "byte";
                break;
            case TypeCode.Int16:
                valueStr = ((short)value).ToString(CultureInfo.InvariantCulture);
                typeHint = "int16";
                break;
            case TypeCode.UInt16:
                valueStr = ((ushort)value).ToString(CultureInfo.InvariantCulture);
                typeHint = "uint16";
                break;
            case TypeCode.UInt32:
                valueStr = ((uint)value).ToString(CultureInfo.InvariantCulture);
                typeHint = "uint32";
                break;
            case TypeCode.UInt64:
                valueStr = ((ulong)value).ToString(CultureInfo.InvariantCulture);
                typeHint = "uint64";
                break;
            case TypeCode.SByte:
                valueStr = ((sbyte)value).ToString(CultureInfo.InvariantCulture);
                typeHint = "sbyte";
                break;
            case TypeCode.Char:
                valueStr = EscapeString(value.ToString()!);
                typeHint = "char";
                break;
            default:
                // Check special types
                if (ReferenceEquals(underlyingType, GuidType))
                {
                    valueStr = $"\"{((Guid)value).ToString("D")}\"";
                    typeHint = "guid";
                }
                else if (ReferenceEquals(underlyingType, DateTimeOffsetType))
                {
                    valueStr = $"\"{((DateTimeOffset)value).ToString("O", CultureInfo.InvariantCulture)}\"";
                    typeHint = "datetimeoffset";
                }
                else if (ReferenceEquals(underlyingType, TimeSpanType))
                {
                    valueStr = $"\"{((TimeSpan)value).ToString("c", CultureInfo.InvariantCulture)}\"";
                    typeHint = "timespan";
                }
                else if (underlyingType.IsEnum)
                {
                    // Serialize enum as numeric value (not name) for token efficiency
                    // The @types section contains the mapping
                    var enumUnderlyingType = Enum.GetUnderlyingType(underlyingType);
                    var numericValue = Convert.ChangeType(value, enumUnderlyingType);
                    valueStr = Convert.ToString(numericValue, CultureInfo.InvariantCulture)!;
                    typeHint = "enum";
                }
                else
                {
                    return false;
                }
                break;
        }

        context.Write(valueStr);

        // Add inline type hint if enabled
        if (context.Options.UseInlineTypeHints && typeHint != null)
        {
            context.Write($" <{typeHint}>");
        }

        return true;
    }

    /// <summary>
    /// Write complex object.
    /// </summary>
    private static void WriteObject(object value, Type type, ToonSerializationContext context)
    {
        var metadata = GetTypeMetadata(type);

        // Write reference ID if this is a multi-referenced object
        if (context.ReferenceHandling != ReferenceHandlingMode.None && context.ShouldWriteRef(value, out var refId))
        {
            context.Write($"@{refId} ");
            context.MarkAsWritten(value, refId);
        }

        // Write type name if enabled
        if (context.Options.WriteTypeNames)
        {
            context.Write(metadata.ShortTypeName);
        }

        context.WriteLine(" {");
        context.CurrentIndentLevel++;

        // Write properties
        foreach (var prop in metadata.Properties)
        {
            var propValue = prop.GetValue(value);

            // Skip null/default values if option is set
            if (context.Options.OmitDefaultValues && prop.IsDefaultValue(propValue))
                continue;

            context.WriteIndent();
            context.Write(prop.Name);

            // Token optimization: no spaces around '=' when indentation is disabled
            if (context.Options.UseIndentation)
            {
                context.Write(" = ");
            }
            else
            {
                context.Write('=');
            }

            if (propValue == null)
            {
                context.WriteLine("null");
            }
            else
            {
                WriteValue(propValue, prop.PropertyType, context);
                context.WriteLine();
            }
        }

        context.CurrentIndentLevel--;
        context.WriteIndent();
        context.Write("}");
    }

    /// <summary>
    /// Write array/collection.
    /// </summary>
    private static void WriteArray(IEnumerable enumerable, Type type, ToonSerializationContext context)
    {
        var elementType = GetCollectionElementType(type) ?? typeof(object);

        // Get count if possible
        var count = 0;
        if (enumerable is ICollection collection)
        {
            count = collection.Count;
        }
        else
        {
            // Fallback: enumerate to count
            foreach (var _ in enumerable) count++;
        }

        // Write array header with optional type hint and count
        if (context.Options.ShowCollectionCount)
        {
            var elementTypeName = GetTypeDisplayName(elementType);
            context.Write($"<{elementTypeName}[]> (count: {count}) ");
        }

        context.WriteLine("[");
        context.CurrentIndentLevel++;

        foreach (var item in enumerable)
        {
            context.WriteIndent();
            WriteValue(item, item?.GetType() ?? elementType, context);
            context.WriteLine();
        }

        context.CurrentIndentLevel--;
        context.WriteIndent();
        context.Write("]");
    }

    /// <summary>
    /// Get C# type display name for a type (used in array hints).
    /// Uses shared AcSerializerCommon.GetCSharpTypeName for consistency.
    /// </summary>
    private static string GetTypeDisplayName(Type type)
    {
        return AcSerializerCommon.GetCSharpTypeName(type, useShortNames: true);
    }

    /// <summary>
    /// Format multi-line string with triple-quote syntax.
    /// </summary>
    private static string FormatMultiLineString(string value, ToonSerializationContext context)
    {
        var sb = new StringBuilder();
        sb.Append("\"\"\"");

        if (context.Options.UseIndentation)
        {
            sb.AppendLine();

            // Split into lines and indent each
            var lines = value.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
            foreach (var line in lines)
            {
                for (var i = 0; i <= context.CurrentIndentLevel; i++)
                {
                    sb.Append(context.Options.IndentString);
                }
                sb.AppendLine(line);
            }

            // Closing quotes with proper indentation
            for (var i = 0; i < context.CurrentIndentLevel; i++)
            {
                sb.Append(context.Options.IndentString);
            }
            sb.Append("\"\"\"");
        }
        else
        {
            sb.Append(value);
            sb.Append("\"\"\"");
        }

        return sb.ToString();
    }

    /// <summary>
    /// Write dictionary.
    /// </summary>
    private static void WriteDictionary(IDictionary dictionary, ToonSerializationContext context)
    {
        // Write dictionary header with count and type information
        if (context.Options.ShowCollectionCount)
        {
            var dictType = dictionary.GetType();
            var typeDisplayName = GetDictionaryTypeDisplayName(dictType);
            context.Write($"<{typeDisplayName}> (count: {dictionary.Count}) ");
        }

        context.WriteLine("{");
        context.CurrentIndentLevel++;

        foreach (DictionaryEntry entry in dictionary)
        {
            context.WriteIndent();

            // Write key
            var keyType = entry.Key?.GetType() ?? typeof(object);
            WriteValue(entry.Key, keyType, context);

            context.Write(" => ");

            // Write value
            var valueType = entry.Value?.GetType() ?? typeof(object);
            WriteValue(entry.Value, valueType, context);

            context.WriteLine();
        }

        context.CurrentIndentLevel--;
        context.WriteIndent();
        context.Write("}");
    }

    /// <summary>
    /// Get type display name for dictionary (e.g., "Dictionary&lt;string, decimal&gt;").
    /// </summary>
    private static string GetDictionaryTypeDisplayName(Type dictType)
    {
        if (IsDictionaryType(dictType, out var keyType, out var valueType))
        {
            var keyTypeName = GetSimpleTypeName(keyType);
            var valueTypeName = GetSimpleTypeName(valueType);
            return $"Dictionary<{keyTypeName}, {valueTypeName}>";
        }

        return "dict";
    }

    /// <summary>
    /// Get simple type name for dictionary type parameters.
    /// </summary>
    private static string GetSimpleTypeName(Type? type)
    {
        if (type == null) return "object";

        var underlying = Nullable.GetUnderlyingType(type);
        var isNullable = underlying != null;
        var actualType = underlying ?? type;

        var baseName = Type.GetTypeCode(actualType) switch
        {
            TypeCode.Int32 => "int",
            TypeCode.Int64 => "long",
            TypeCode.Double => "double",
            TypeCode.Decimal => "decimal",
            TypeCode.Single => "float",
            TypeCode.Boolean => "bool",
            TypeCode.String => "string",
            TypeCode.DateTime => "DateTime",
            TypeCode.Byte => "byte",
            TypeCode.Int16 => "short",
            TypeCode.UInt16 => "ushort",
            TypeCode.UInt32 => "uint",
            TypeCode.UInt64 => "ulong",
            TypeCode.SByte => "sbyte",
            TypeCode.Char => "char",
            _ => actualType.Name
        };

        return isNullable ? baseName + "?" : baseName;
    }
}
