// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis;

namespace Microsoft.Testing.Platform.OutputDevice.Terminal;

[Embedded]
internal sealed class ExceptionFlattener
{
    internal static FlatException[] Flatten(string? errorMessage, Exception? exception)
    {
        if (errorMessage is null && exception is null)
        {
            return [];
        }

        string? message = !RoslynString.IsNullOrWhiteSpace(errorMessage) ? errorMessage : exception?.Message;
        string? type = exception?.GetType().FullName;
        string? stackTrace = exception?.StackTrace;
        var flatException = new FlatException(message, type, stackTrace);

        List<FlatException> flatExceptions = [flatException];

        // Add all inner exceptions. This will flatten top level AggregateExceptions,
        // and all AggregateExceptions that are directly in AggregateExceptions, but won't expand
        // AggregateExceptions that are in non-aggregate exception inner exceptions.
        IEnumerable<Exception?> aggregateExceptions = exception switch
        {
            AggregateException aggregate => aggregate.Flatten().InnerExceptions,
            _ => new Exception?[] { exception?.InnerException },
        };

        foreach (Exception? aggregate in aggregateExceptions)
        {
            Exception? currentException = aggregate;
            while (currentException is not null)
            {
                flatExceptions.Add(new FlatException(
                    currentException.Message,
                    currentException.GetType().FullName,
                    currentException.StackTrace));

                currentException = currentException.InnerException;
            }
        }

        return [.. flatExceptions];
    }
}

[Embedded]
internal sealed record FlatException(string? ErrorMessage, string? ErrorType, string? StackTrace);
