namespace Json2CSharpCodeGenerator.Lib;

public interface IJsonClassGeneratorConfig
{
    /// <summary>
    /// The C# <c>namespace</c> or Java <c>package</c> that the generated types will reside in.<br />
    /// <see langword="null"/> by default. If null/empty/whitespace then no enclosing namespace will be written in the output.
    /// </summary>
    string Namespace { get; set; }

    /// <summary>
    /// The C# <c>namespace</c> or Java <c>package</c> that &quot;secondary&quot; generated types will reside in.<br />
    /// <see langword="null"/> by default.
    /// </summary>
    string SecondaryNamespace { get; set; }

    OutputTypes OutputType { get; set; }
    OutputCollectionType CollectionType { get; set; }

    /// <summary>Options contained within are only respected when <see cref="OutputType"/> == <see cref="OutputTypes.MutableClass"/>.</summary>
    MutableClassConfig MutableClasses { get; }

    JsonLibrary AttributeLibrary { get; set; }
    JsonPropertyAttributeUsage AttributeUsage { get; set; }

    bool InternalVisibility { get; set; }
    bool NoHelperClass { get; set; }

    /// <summary>
    /// When true, then the generated C# and VB.NET classes will have PascalCase property names, which means that <c>[JsonProperty]</c> or <c>[JsonPropertyName]</c> will be applied to all properties if the source JSON uses camelCase names, regardless of <see cref="AttributeUsage"/>.<br />
    /// When false, then the generated C# and VB.NET classes' property names will use the same names as the source JSON (except when a JSON property name cannot be represented by a C# identifier).
    /// </summary>
    bool UsePascalCase { get; set; }

    bool UseNestedClasses { get; set; }

    /// <summary>Name of the outer class. Only used when <c><see cref="UseNestedClasses"/> == <see langword="true"/></c>.</summary>
    string MainClass { get; set; }

    /// <summary>When <see langword="true"/>, then <see cref="System.Reflection.ObfuscationAttribute"/> will be applied to generated types.</summary>
    bool ApplyObfuscationAttributes { get; set; }

    // bool SingleFile { get; set; }
    bool UseThisKeyWord { get; set; }
    ICodeBuilder CodeWriter { get; set; }
    bool AlwaysUseNullableValues { get; set; }
    bool ExamplesInDocumentation { get; set; }
    bool RemoveToJson { get; set; }
    bool RemoveFromJson { get; set; }
    bool RemoveConstructors { get; set; }
}

public enum OutputTypes
{
    /// <summary>
    /// C#: Mutable <c>class</c> types.<br />
    /// VB.NET: Mutable <c>Class</c> types.<br />
    /// Java: Mutable Bean <c>class</c>
    /// </summary>
    MutableClass,

    /// <summary>
    /// C#: Immutable <c>class</c> types. Using <c>[JsonConstructor]</c>.<br />
    /// VB.NET: Immutable <c>Class</c> types. Using <c>[JsonConstructor]</c>.<br />
    /// Java: Not yet implemented. TODO.
    /// </summary>
    ImmutableClass,

    /// <summary>
    /// C#: Immutable <c>record</c> types.<br />
    /// VB.NET: Not supported.<br />
    /// Java: Not supported.<br />
    /// </summary>
    ImmutableRecord
}

public enum OutputCollectionType
{
    /// <summary>Expose collections as <c>T[]</c>.</summary>
    Array,

    /// <summary>
    /// C#/VB.NET: Expose collections as <c><see cref="List{T}"/></c>.<br />
    /// Java: Uses <c>ArrayList&lt;T&gt;</c>
    /// </summary>
    MutableList,

    /// <summary>
    /// C#/VB.NET: Expose collections as <c><see cref="IReadOnlyList{T}"/></c>.<br />
    /// Java: Not supported.
    /// </summary>
    IReadOnlyList,

    /// <summary>
    /// C#/VB.NET: Expose collections as <c>System.Collections.Immutable.ImmutableArray&lt;T&gt;</c>.<br />
    /// Java: Not supported.
    /// </summary>
    /// <remarks><c>ImmutableArray</c> is preferred over <c>ImmutableList</c> when append functionality isn't required.</remarks>
    ImmutableArray
}

public enum OutputMembers
{
    /// <summary>C# and VB.NET: Uses auto-properties. Java: uses getter/setter methods.</summary>
    AsProperties,
    AsPublicFields,
    //      AsPublicPropertiesOverPrivateFields // TODO
}

public enum JsonLibrary
{
    /// <summary>Use the <see cref="Newtonsoft.Json.JsonPropertyAttribute"/> on generated C# class properties.</summary>
    NewtonsoftJson,

    /// <summary>Use the <c>[JsonPropertyName]</c> attribute on generated C# class properties.</summary>
    SystemTextJson
}

public enum JsonPropertyAttributeUsage
{
    /// <summary>The <c>[JsonProperty]</c> or <c>[JsonPropertyName]</c> attributes will be applied to all properties.</summary>
    Always,

    /// <summary>The <c>[JsonProperty]</c> or <c>[JsonPropertyName]</c> attributes will only be applied to properties with names that cannot be expressed as C# identifiers.</summary>
    OnlyWhenNecessary
}

public class MutableClassConfig
{
    /// <summary>
    /// When <see langword="true"/>, then all properties for collections are read-only, though the actual collection-type can still be mutable. e.g. <c>public List&lt;String&gt; StringValues { get; }</c><br />
    /// When <see langword="false"/>, then all properties for collections are read-only, though the actual collection-type can still be mutable. e.g. <c>public List&lt;String&gt; StringValues { get; set; }</c><br />
    /// Default is <see langword="false"/>.
    /// </summary>
    public bool ReadOnlyCollectionProperties { get; set; }

    public OutputMembers Members { get; set; } = OutputMembers.AsProperties;
}

public static class JsonClassGeneratorConfigExtensions
{
    /// <summary>
    /// Never returns <see langword="null"/>. Returns either:
    /// <list type="bullet">
    /// <item>&quot;<c>[JsonPropertyName(&quot;<paramref name="field"/>.<see cref="FieldInfo.JsonMemberName"/>&quot;)]</c>&quot; (for <c>System.Text.Json</c>).</item>
    /// <item>&quot;<c>[JsonProperty(&quot;<paramref name="field"/>.<see cref="FieldInfo.JsonMemberName"/>&quot;)]</c>&quot; (for <c>Newtonsoft.Json</c>).</item>
    /// <item>&quot;<c>[property: JsonPropertyName(&quot;<paramref name="field"/>.<see cref="FieldInfo.JsonMemberName"/>&quot;)]</c>&quot; (for <c>System.Text.Json</c>) when <see cref="IJsonClassGeneratorConfig.RecordTypes"/> is <see langword="true"/>.</item>
    /// <item>&quot;<c>[property: JsonProperty(&quot;<paramref name="field"/>.<see cref="FieldInfo.JsonMemberName"/>&quot;)]</c>&quot; (for <c>Newtonsoft.Json</c>) when <see cref="IJsonClassGeneratorConfig.RecordTypes"/> is <see langword="true"/>.</item>
    /// <item>An empty string depending on <paramref name="config"/> and <see cref="FieldInfo.ContainsSpecialChars"/>.</item>
    /// </list>
    /// </summary>
    /// <param name="config">Required. Cannot be <see langword="null"/>.</param>
    /// <param name="field">Required. Cannot be <see langword="null"/>.</param>
    /// <returns></returns>
    public static string GetCSharpJsonAttributeCode(this IJsonClassGeneratorConfig config, FieldInfo field)
    {
        if (config is null)
            throw new ArgumentNullException(nameof(config));
        if (field is null)
            throw new ArgumentNullException(nameof(field));

        //

        if (UsePropertyAttribute(config, field))
        {
            bool usingRecordTypes = config.OutputType == OutputTypes.ImmutableRecord;
            string attributeTarget = usingRecordTypes ? "property: " : string.Empty;

            return config.AttributeLibrary switch
            {
                JsonLibrary.NewtonsoftJson => $"[{attributeTarget}JsonProperty(\"{field.JsonMemberName}\")]",
                JsonLibrary.SystemTextJson => $"[{attributeTarget}JsonPropertyName(\"{field.JsonMemberName}\")]",
                _ => throw new InvalidOperationException("Unrecognized " + nameof(config.AttributeLibrary) + " value: " + config.AttributeLibrary),
            };
        }
        else
        {
            return string.Empty;
        }
    }

    private static bool UsePropertyAttribute(IJsonClassGeneratorConfig config, FieldInfo field)
    {
        return config.AttributeUsage switch
        {
            JsonPropertyAttributeUsage.Always => true,
            JsonPropertyAttributeUsage.OnlyWhenNecessary => field.ContainsSpecialChars,
            _ => throw new InvalidOperationException("Unrecognized " + nameof(config.AttributeUsage) + " value: " + config.AttributeUsage),
        };
    }

    public static bool HasNamespace(this IJsonClassGeneratorConfig config) => !string.IsNullOrEmpty(config.Namespace);
}