// <auto-generated/>

#nullable disable

using System;
using System.Buffers.Text;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Xml;
using System.Xml.Linq;

namespace SampleTypeSpec
{
    internal static partial class ModelSerializationExtensions
    {
        internal static readonly ModelReaderWriterOptions WireOptions = new ModelReaderWriterOptions("W");
        internal static readonly JsonDocumentOptions JsonDocumentOptions = new JsonDocumentOptions
        {
            MaxDepth = 256
        };
        internal static readonly XmlWriterSettings XmlWriterSettings = new XmlWriterSettings
        {
            Encoding = new UTF8Encoding(false)
        };
        private static readonly XmlReaderSettings XmlReaderSettings = new XmlReaderSettings
        {
            DtdProcessing = DtdProcessing.Prohibit,
            XmlResolver = null,
            MaxCharactersInDocument = 30000000,
            IgnoreProcessingInstructions = true,
            IgnoreComments = true
        };

        public static object GetObject(this JsonElement element)
        {
            switch (element.ValueKind)
            {
                case JsonValueKind.String:
                    return element.GetString();
                case JsonValueKind.Number:
                    if (element.TryGetInt32(out int intValue))
                    {
                        return intValue;
                    }
                    if (element.TryGetInt64(out long longValue))
                    {
                        return longValue;
                    }
                    return element.GetDouble();
                case JsonValueKind.True:
                    return true;
                case JsonValueKind.False:
                    return false;
                case JsonValueKind.Undefined:
                case JsonValueKind.Null:
                    return null;
                case JsonValueKind.Object:
                    Dictionary<string, object> dictionary = new Dictionary<string, object>();
                    foreach (var jsonProperty in element.EnumerateObject())
                    {
                        dictionary.Add(jsonProperty.Name, jsonProperty.Value.GetObject());
                    }
                    return dictionary;
                case JsonValueKind.Array:
                    List<object> list = new List<object>();
                    foreach (var item in element.EnumerateArray())
                    {
                        list.Add(item.GetObject());
                    }
                    return list.ToArray();
                default:
                    throw new NotSupportedException($"Not supported value kind {element.ValueKind}");
            }
        }

        public static byte[] GetBytesFromBase64(this JsonElement element, string format)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }

            return format switch
            {
                "U" => TypeFormatters.FromBase64UrlString(element.GetRequiredString()),
                "D" => element.GetBytesFromBase64(),
                _ => throw new ArgumentException($"Format is not supported: '{format}'", nameof(format))
            };
        }

        public static DateTimeOffset GetDateTimeOffset(this JsonElement element, string format) => format switch
        {
            "U" when element.ValueKind == JsonValueKind.Number => DateTimeOffset.FromUnixTimeSeconds(element.GetInt64()),
            _ => TypeFormatters.ParseDateTimeOffset(element.GetString(), format)
        };

        public static TimeSpan GetTimeSpan(this JsonElement element, string format) => TypeFormatters.ParseTimeSpan(element.GetString(), format);

        public static char GetChar(this JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.String)
            {
                string text = element.GetString();
                if (text == null || text.Length != 1)
                {
                    throw new NotSupportedException($"Cannot convert \"{text}\" to a char");
                }
                return text[0];
            }
            else
            {
                throw new NotSupportedException($"Cannot convert {element.ValueKind} to a char");
            }
        }

        [Conditional("DEBUG")]
        public static void ThrowNonNullablePropertyIsNull(this JsonProperty @property)
        {
            throw new JsonException($"A property '{@property.Name}' defined as non-nullable but received as null from the service. This exception only happens in DEBUG builds of the library and would be ignored in the release build");
        }

        public static string GetRequiredString(this JsonElement element)
        {
            string value = element.GetString();
            if (value == null)
            {
                throw new InvalidOperationException($"The requested operation requires an element of type 'String', but the target element has type '{element.ValueKind}'.");
            }
            return value;
        }

        public static void WriteStringValue(this Utf8JsonWriter writer, DateTimeOffset value, string format)
        {
            writer.WriteStringValue(TypeFormatters.ToString(value, format));
        }

        public static void WriteStringValue(this Utf8JsonWriter writer, DateTime value, string format)
        {
            writer.WriteStringValue(TypeFormatters.ToString(value, format));
        }

        public static void WriteStringValue(this Utf8JsonWriter writer, TimeSpan value, string format)
        {
            writer.WriteStringValue(TypeFormatters.ToString(value, format));
        }

        public static void WriteStringValue(this Utf8JsonWriter writer, char value)
        {
            writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture));
        }

        public static void WriteBase64StringValue(this Utf8JsonWriter writer, byte[] value, string format)
        {
            if (value == null)
            {
                writer.WriteNullValue();
                return;
            }
            switch (format)
            {
                case "U":
                    writer.WriteStringValue(TypeFormatters.ToBase64UrlString(value));
                    break;
                case "D":
                    writer.WriteBase64StringValue(value);
                    break;
                default:
                    throw new ArgumentException($"Format is not supported: '{format}'", nameof(format));
            }
        }

        public static void WriteNumberValue(this Utf8JsonWriter writer, DateTimeOffset value, string format)
        {
            if (format != "U")
            {
                throw new ArgumentOutOfRangeException(nameof(format), "Only 'U' format is supported when writing a DateTimeOffset as a Number.");
            }
            writer.WriteNumberValue(value.ToUnixTimeSeconds());
        }

        public static void WriteObjectValue<T>(this Utf8JsonWriter writer, T value, ModelReaderWriterOptions options = null)
        {
            switch (value)
            {
                case null:
                    writer.WriteNullValue();
                    break;
                case IJsonModel<T> jsonModel:
                    jsonModel.Write(writer, options ?? WireOptions);
                    break;
                case byte[] bytes:
                    writer.WriteBase64StringValue(bytes);
                    break;
                case BinaryData bytes0:
                    writer.WriteBase64StringValue(bytes0);
                    break;
                case JsonElement json:
                    json.WriteTo(writer);
                    break;
                case int i:
                    writer.WriteNumberValue(i);
                    break;
                case decimal d:
                    writer.WriteNumberValue(d);
                    break;
                case double d0:
                    if (double.IsNaN(d0))
                    {
                        writer.WriteStringValue("NaN");
                    }
                    else
                    {
                        writer.WriteNumberValue(d0);
                    }
                    break;
                case float f:
                    writer.WriteNumberValue(f);
                    break;
                case long l:
                    writer.WriteNumberValue(l);
                    break;
                case string s:
                    writer.WriteStringValue(s);
                    break;
                case bool b:
                    writer.WriteBooleanValue(b);
                    break;
                case Guid g:
                    writer.WriteStringValue(g);
                    break;
                case DateTimeOffset dateTimeOffset:
                    writer.WriteStringValue(dateTimeOffset, "O");
                    break;
                case DateTime dateTime:
                    writer.WriteStringValue(dateTime, "O");
                    break;
                case IEnumerable<KeyValuePair<string, object>> enumerable:
                    writer.WriteStartObject();
                    foreach (var pair in enumerable)
                    {
                        writer.WritePropertyName(pair.Key);
                        writer.WriteObjectValue<object>(pair.Value, options);
                    }
                    writer.WriteEndObject();
                    break;
                case IEnumerable<object> objectEnumerable:
                    writer.WriteStartArray();
                    foreach (var item in objectEnumerable)
                    {
                        writer.WriteObjectValue<object>(item, options);
                    }
                    writer.WriteEndArray();
                    break;
                case TimeSpan timeSpan:
                    writer.WriteStringValue(timeSpan, "P");
                    break;
                default:
                    throw new NotSupportedException($"Not supported type {value.GetType()}");
            }
        }

        public static void WriteObjectValue(this Utf8JsonWriter writer, object value, ModelReaderWriterOptions options = null)
        {
            writer.WriteObjectValue<object>(value, options);
        }

        public static BinaryData GetUtf8Bytes(this JsonElement element)
        {
#if NET9_0_OR_GREATER
            return new global::System.BinaryData(global::System.Runtime.InteropServices.JsonMarshal.GetRawUtf8Value(element).ToArray());
#else
            return BinaryData.FromString(element.GetRawText());
#endif
        }

        public static ReadOnlySpan<byte> SliceToStartOfPropertyName(this ReadOnlySpan<byte> jsonPath)
        {
            ReadOnlySpan<byte> local = jsonPath;
            if (local.Length < 3)
            {
                return ReadOnlySpan<byte>.Empty;
            }
            if (local[0] != '$')
            {
                return ReadOnlySpan<byte>.Empty;
            }
            return local.Length >= 4 && local[1] == '[' && (local[2] == '\'' || local[2] == '"') ? local.Slice(3) : ReadOnlySpan<byte>.Empty;
        }

        public static string GetFirstPropertyName(this ReadOnlySpan<byte> jsonPath, out int bytesConsumed)
        {
            ReadOnlySpan<byte> local = jsonPath;
            for (bytesConsumed = 0; bytesConsumed < local.Length; bytesConsumed++)
            {
                byte current = local[bytesConsumed];
                if (current == '.')
                {
                    break;
                }
                else
                {
                    if (current == '\'' || current == '"')
                    {
                        if (bytesConsumed + 1 < local.Length && local[bytesConsumed + 1] == ']')
                        {
                            break;
                        }
                    }
                }
            }

            string key;
#if NET6_0_OR_GREATER
            key = global::System.Text.Encoding.UTF8.GetString(local.Slice(0, bytesConsumed));
#else
            key = Encoding.UTF8.GetString(local.Slice(0, bytesConsumed).ToArray());
#endif
            bytesConsumed += jsonPath.Length - local.Length;
            return key;
        }

        public static bool TryGetIndex(this ReadOnlySpan<byte> indexSlice, out int index, out int bytesConsumed)
        {
            index = -1;
            bytesConsumed = 0;

            if (indexSlice.IsEmpty || indexSlice[0] != '[')
            {
                return false;
            }

            indexSlice = indexSlice.Slice(1);
            if (indexSlice.IsEmpty || indexSlice[0] == '-')
            {
                return false;
            }

            int indexEnd = indexSlice.Slice(1).IndexOf((byte)']');
            if (indexEnd < 0)
            {
                return false;
            }

            return Utf8Parser.TryParse(indexSlice.Slice(0, indexEnd + 1), out index, out bytesConsumed);
        }

        public static ReadOnlySpan<byte> GetRemainder(this ReadOnlySpan<byte> jsonPath, int index)
        {
            return index >= jsonPath.Length ? ReadOnlySpan<byte>.Empty : jsonPath[index] == '.' ? jsonPath.Slice(index) : jsonPath.Slice(index + 2);
        }

        public static DateTimeOffset GetDateTimeOffset(this XElement element, string format) => format switch
        {
            "U" => DateTimeOffset.FromUnixTimeSeconds((long)element),
            _ => TypeFormatters.ParseDateTimeOffset(element.Value, format)
        };

        public static TimeSpan GetTimeSpan(this XElement element, string format) => TypeFormatters.ParseTimeSpan(element.Value, format);

        public static byte[] GetBytesFromBase64(this XElement element, string format) => format switch
        {
            "U" => TypeFormatters.FromBase64UrlString(element.Value),
            "D" => Convert.FromBase64String(element.Value),
            _ => throw new ArgumentException("Format is not supported: ", nameof(format))
        };

        public static void WriteStringValue(this XmlWriter writer, DateTimeOffset value, string format)
        {
            writer.WriteValue(TypeFormatters.ToString(value, format));
        }

        public static void WriteStringValue(this XmlWriter writer, TimeSpan value, string format)
        {
            writer.WriteValue(TypeFormatters.ToString(value, format));
        }

        public static void WriteBase64StringValue(this XmlWriter writer, byte[] value, string format)
        {
            writer.WriteValue(TypeFormatters.ToString(value, format));
        }

        public static void WriteObjectValue<T>(this XmlWriter writer, T value, ModelReaderWriterOptions options = null, string nameHint = null)
        {
            switch (value)
            {
                case IPersistableModel<T> persistableModel:
                    BinaryData data = ModelReaderWriter.Write(persistableModel, options ?? WireOptions, SampleTypeSpecContext.Default);
                    using (Stream stream = data.ToStream())
                    {
                        using (XmlReader reader = XmlReader.Create(stream, XmlReaderSettings))
                        {
                            reader.MoveToContent();
                            if (nameHint != null)
                            {
                                writer.WriteStartElement(nameHint);
                                reader.ReadStartElement();
                                while (reader.NodeType != XmlNodeType.EndElement)
                                {
                                    writer.WriteNode(reader, true);
                                }
                                writer.WriteEndElement();
                            }
                            else
                            {
                                reader.ReadStartElement();
                                while (reader.NodeType != XmlNodeType.EndElement)
                                {
                                    writer.WriteNode(reader, true);
                                }
                            }
                        }
                    }
                    return;
                default:
                    throw new NotSupportedException($"Not supported type {typeof(T)}");
            }
        }
    }
}
