namespace InnovEnergy.Lib.Utils;

public class TextBlock
{
    private readonly IReadOnlyList<String> _Lines;

    private TextBlock(params String[] lines) => _Lines = lines;

    public override String ToString() => _Lines.JoinLines();

    public static TextBlock Empty { get; } = new TextBlock();
    
    public static TextBlock AlignLeft(IEnumerable<Object> things)
    {
        var lines = things
                   .SelectMany(GetLines)
                   .Unless(String.IsNullOrEmpty)
                   .ToList();

        if (!lines.Any())
            return Empty;

        var width = lines.Max(l => l.Length); 

        var alignedLines = lines
                          .Select(l => l.PadRight(width))
                          .ToArray(lines.Count);
        
        return new TextBlock(alignedLines);
    }

    private static TextBlock AlignRight(IReadOnlyList<Object> things)
    {
        var lines = things
                   .SelectMany(GetLines)
                   .Unless(String.IsNullOrEmpty)
                   .ToList();

        if (!lines.Any())
            return Empty;
        
        var width = lines.Max(l => l.Length);

        var alignedLines = lines
                          .Select(l => l.PadLeft(width))
                          .ToArray(lines.Count);
        
        return new TextBlock(alignedLines);
    }

    public static TextBlock AlignCenterHorizontal(IReadOnlyList<Object> things)
    {
        var lines = things
                   .SelectMany(GetLines)
                   .Where(l => !String.IsNullOrEmpty(l))
                   .ToList();
        
         if (!lines.Any())
            return Empty;
         
        var width = lines.Max(l => l.Length);

        var alignedLines = lines
                          .Select(l => l.PadLeft((width + l.Length) / 2).PadRight(width))
                          .ToArray(lines.Count);
        
        return new TextBlock(alignedLines);
    }
    
    public static TextBlock AlignTop(IReadOnlyList<Object> things)
    {
        var columns = things
                     .Select(GetLines)
                     .Where(c => c.Count > 0)
                     .ToList();
        
        if (!columns.Any())
            return Empty;
        
        var height = columns.Max(l => l.Count);

        var alignedLines = Enumerable
                          .Range(0, height)
                          .Select(l => columns.Select(c => c.ElementAtOr(l, Space(c[0].Length))).Join())
                          .ToArray(height);
        
        return new TextBlock(alignedLines);
    }
    
    
    public static TextBlock AlignBottom(IReadOnlyList<Object> things)
    {
        var columns = things
                     .Select(GetLines)
                     .Where(c => c.Count > 0)
                     .ToList();
        
        if (!columns.Any())
            return Empty;
        
        var height = columns.Max(l => l.Count);

        var alignedLines = Enumerable
                          .Range(0, height)
                          .Select(l => columns.Select(c => c.ElementAtOr(c.Count - height + l, Space(c[0].Length))).Join())
                          .ToArray(height);
        
        return new TextBlock(alignedLines);
    }
    
  
    public static TextBlock AlignCenterVertical(IReadOnlyList<Object> things)
    {
        var columns = things
                     .Select(GetLines)
                     .Where(c => c.Count > 0)
                     .ToList();
        
        if (!columns.Any())
            return Empty;

        var height = columns.Max(l => l.Count);

        var alignedLines = Enumerable
                          .Range(0, height)
                          .Select(l => columns.Select(c => c.ElementAtOr((c.Count - height) / 2 + l, Space(c[0].Length))).Join())
                          .ToArray(height);
        
        return new TextBlock(alignedLines);
    }
    
    public static TextBlock AlignLeft            (params Object?[] things) => AlignLeft            ((IReadOnlyList<Object>) things);
    public static TextBlock AlignRight           (params Object?[] things) => AlignRight           ((IReadOnlyList<Object>) things);
    public static TextBlock AlignTop             (params Object?[] things) => AlignTop             ((IReadOnlyList<Object>) things);  
    public static TextBlock AlignBottom          (params Object?[] things) => AlignBottom          ((IReadOnlyList<Object>) things);
    public static TextBlock AlignCenterVertical  (params Object?[] things) => AlignCenterVertical  ((IReadOnlyList<Object>) things);
    public static TextBlock AlignCenterHorizontal(params Object?[] things) => AlignCenterHorizontal((IReadOnlyList<Object>) things);
    
    public static TextBlock FromString(String thing) => AlignLeft(thing);
    
    public TextBlock Box()
    {
        var width = _Lines.Any() ? _Lines.Max(l => l.Length) : 0;

        var hLine  = "".PadRight(width + 2, '─');
        var top    = "┌" + hLine + "┐";
        var bottom = "└" + hLine + "┘";

        var lines = _Lines
                    .Select(l => l.SurroundWith(" "))
                    .Select(l => l.SurroundWith("│"))
                    .Prepend(top)
                    .Append(bottom)
                    .ToArray(_Lines.Count + 2);
        
        return new TextBlock(lines);
    }
    
    public TextBlock TitleBox(String title)
    {
        var linesWidth = _Lines.Any() ? _Lines.Max(l => l.Length) : 0;
        var titleWidth = title.Length;

        var width = Math.Max(linesWidth, titleWidth);
        
        var hLine   = "".PadRight(width + 2, '─');
        var top     = "┌" + hLine + "┐";
        var between = "├" + hLine + "┤";
        var bottom  = "└" + hLine + "┘";

        var centeredTitle = "│ " + title.PadLeft((width + title.Length) / 2).PadRight(width) + " │";

        var lines = _Lines
                    .Select(l => l.PadRight(width))
                    .Select(l => l.SurroundWith(" "))
                    .Select(l => l.SurroundWith("│"))
                    .Prepend(between)
                    .Prepend(centeredTitle)
                    .Prepend(top)
                    .Append(bottom)
                    .ToArray();
        
        return new TextBlock(lines);
    }

    public static TextBlock Spacer(Int32 n)
    {
        return new TextBlock(Enumerable.Repeat("".PadRight(n), n).ToArray(n));
    }
    
    public static TextBlock HorizontalSpace(Int32 n)
    {
        return new TextBlock("".PadRight(n));
    }
    
    public static TextBlock VerticalSpace(Int32 n)
    {
        return new TextBlock(Enumerable.Repeat("", n).ToArray(n));
    }


    private static IReadOnlyList<String> GetLines(Object? t)
    {
        return t switch
        {
            TextBlock tb => tb._Lines,
            null         => Array.Empty<String>(),
            _            => t.ToString()!.SplitLines()
        };
    }

    private static String Space(Int32 totalWidth)
    {
        return "".PadRight(totalWidth);
    }
}