// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using System.Text;

namespace Aspire.Hosting.Dapr;

internal delegate IEnumerable<object> CommandLineArgBuilder();

internal sealed record CommandLine(string FileName, IEnumerable<object> Arguments)
{
    public string ArgumentString
    {
        get
        {
            StringBuilder builder = new();

            var args = this.Arguments.ToList();

            for (int i = 0; i < args.Count; i++)
            {
                var arg = args[i];

                if (arg is not null)
                {
                    if (i > 0)
                    {
                        builder.Append(' ');
                    }

                    builder.Append(arg);
                }
            }

            return builder.ToString();
        }
    }
}

internal static class CommandLineBuilder
{
    public static CommandLine Create(string fileName, params CommandLineArgBuilder[] argBuilders)
    {
        return new CommandLine(fileName, argBuilders.SelectMany(builder => builder()));
    }
}

internal static class CommandLineArgs
{
    public static CommandLineArgBuilder Args(params string[] args)
    {
        return Args((IEnumerable<string>)args);
    }

    public static CommandLineArgBuilder Args(IEnumerable<string>? args)
    {
        return () => (args ?? Enumerable.Empty<string>());
    }

    public static CommandLineArgBuilder Command(params string[] commands)
    {
        return () => commands;
    }

    public static CommandLineArgBuilder Command(CommandLine commandLine)
    {
        return Command(new[] { commandLine.FileName, commandLine.ArgumentString });
    }

    public static CommandLineArgBuilder Flag(string name)
    {
        return Flag(name, true);
    }

    public static CommandLineArgBuilder Flag(string name, bool? value)
    {
        return () => value == true ? new[] { name } : Enumerable.Empty<string>();
    }

    public static CommandLineArgBuilder NamedArg<T>(string name, T value, bool assignValue = false) where T : struct
    {
        return () =>
        {
            string? stringValue = Convert.ToString(value, CultureInfo.InvariantCulture);

            return stringValue is not null
                ? NamedStringArg(name, stringValue, assignValue)()
                : Enumerable.Empty<string>();
        };
    }

    public static CommandLineArgBuilder NamedArg<T>(string name, T? value, bool assignValue = false) where T : struct
    {
        return () =>
            value.HasValue
                ? NamedArg(name, value.Value, assignValue)()
                : Enumerable.Empty<string>();
    }

    public static CommandLineArgBuilder NamedArg(string name, string? value, bool assignValue = false)
    {
        return () =>
        {
            return value is not null
                ? NamedStringArg(name, value, assignValue)()
                : Enumerable.Empty<string>();
        };
    }

    public static CommandLineArgBuilder ModelNamedArg<T>(string name, T value, bool assignValue = false) where T : struct
    {
        return () =>
        {
            string? stringValue = Convert.ToString(value, CultureInfo.InvariantCulture);

            return stringValue is not null
                ? ModelNamedStringArg(name, stringValue, assignValue)()
                : Enumerable.Empty<string>();
        };
    }

    public static CommandLineArgBuilder ModelNamedArg<T>(string name, T? value, bool assignValue = false) where T : struct
    {
        return () =>
            value.HasValue
                ? ModelNamedArg(name, value.Value, assignValue)()
                : Enumerable.Empty<string>();
    }

    public static CommandLineArgBuilder ModelNamedArg(string name, string? value, bool assignValue = false)
    {
        return () =>
        {
            return value is not null
                ? ModelNamedStringArg(name, value, assignValue)()
                : Enumerable.Empty<string>();
        };
    }

    public static CommandLineArgBuilder NamedArg(string name, IEnumerable<string>? values, bool assignValue = false)
    {
        return () =>
        {
            return (values ?? Enumerable.Empty<string>()).SelectMany(value => NamedArg(name, value, assignValue)());
        };
    }

    public static CommandLineArgBuilder ModelNamedArg(string name, IEnumerable<string>? values, bool assignValue = false)
    {
        return () =>
        {
            return (values ?? Enumerable.Empty<string>()).SelectMany(value => ModelNamedArg(name, value, assignValue)());
        };
    }

    private static readonly char[] s_reservedChars = new[] { ' ', '&', '|', '(', ')', '<', '>', '^' };

    private static CommandLineArgBuilder NamedStringArg(string name, string value, bool assignValue)
    {
        bool hasReservedChars = value.Any(c => s_reservedChars.Contains(c)) == true;

        return () =>
        {
            return assignValue
                ? new[] { $"\"{name}={value}\"" }
                : new[] { name, hasReservedChars ? $"\"{value}\"" : value };
        };
    }

    private static CommandLineArgBuilder ModelNamedStringArg(string name, string value, bool assignValue)
    {
        return () =>
        {
            return assignValue
                ? new[] { $"{name}={value}" }
                : new[] { name, value };
        };
    }

    public static CommandLineArgBuilder ModelNamedObjectArg(string name, object value)
    {
        return () =>
        {
            return [name, value];
        };
    }

    public static CommandLineArgBuilder ModelNamedObjectArg<T>(string name, object value, bool assignValue = false) where T : struct
    {
        return () =>
        {
            string? stringValue = Convert.ToString(value, CultureInfo.InvariantCulture);

            return stringValue is not null
                ? ModelNamedStringArg(name, stringValue, assignValue)()
                : Enumerable.Empty<string>();
        };
    }

    public static CommandLineArgBuilder PostOptionsArgs(params CommandLineArgBuilder[] args)
    {
        return PostOptionsArgs(null, args);
    }

    public static CommandLineArgBuilder PostOptionsArgs(string? separator, params CommandLineArgBuilder[] args)
    {
        return PostOptionsArgs(separator, (IEnumerable<CommandLineArgBuilder>)args);
    }

    public static CommandLineArgBuilder PostOptionsArgs(string? separator, IEnumerable<CommandLineArgBuilder> args)
    {
        IEnumerable<object> GeneratePostOptionsArgs()
        {
            bool postOptions = false;

            foreach (var arg in args.SelectMany(builder => builder()))
            {
                if (!postOptions)
                {
                    postOptions = true;

                    yield return separator ?? "--";
                }

                yield return arg;
            }
        }

        return GeneratePostOptionsArgs;
    }
}
