using System.Text;
using NubLang.Syntax;

namespace NubLang.Diagnostics;

public class Diagnostic
{
    public class DiagnosticBuilder
    {
        private readonly DiagnosticSeverity _severity;
        private readonly string _message;
        private SourceSpan? _span;
        private string? _help;

        public DiagnosticBuilder(DiagnosticSeverity severity, string message)
        {
            _severity = severity;
            _message = message;
        }

        public DiagnosticBuilder At(SyntaxNode? node)
        {
            if (node != null)
            {
                _span = SourceSpan.Merge(node.Tokens.Select(x => x.Span));
            }

            return this;
        }

        public DiagnosticBuilder At(Token? token)
        {
            if (token != null)
            {
                At(token.Span);
            }

            return this;
        }

        public DiagnosticBuilder At(SourceSpan? span)
        {
            if (span != null)
            {
                _span = span;
            }

            return this;
        }

        public DiagnosticBuilder At(string filePath, int line, int column)
        {
            _span = new SourceSpan(filePath, new SourceLocation(line, column), new SourceLocation(line, column));
            return this;
        }

        public DiagnosticBuilder WithHelp(string help)
        {
            _help = help;
            return this;
        }

        public Diagnostic Build() => new(_severity, _message, _help, _span);
    }

    public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
    public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message);
    public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message);

    public DiagnosticSeverity Severity { get; }
    public string Message { get; }
    public string? Help { get; }
    public SourceSpan? Span { get; }

    private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceSpan? span)
    {
        Severity = severity;
        Message = message;
        Help = help;
        Span = span;
    }

    public string FormatANSI()
    {
        var sb = new StringBuilder();

        sb.Append(Severity switch
        {
            DiagnosticSeverity.Error => ConsoleColors.Colorize("error", ConsoleColors.Bold + ConsoleColors.Red),
            DiagnosticSeverity.Warning => ConsoleColors.Colorize("warning", ConsoleColors.Bold + ConsoleColors.Yellow),
            DiagnosticSeverity.Info => ConsoleColors.Colorize("info", ConsoleColors.Bold + ConsoleColors.Blue),
            _ => ConsoleColors.Colorize("unknown", ConsoleColors.Bold + ConsoleColors.White)
        });

        if (Span.HasValue)
        {
            sb.Append(ConsoleColors.Colorize($" at {Span.Value}", ConsoleColors.Faint));
        }

        sb.Append(": ");
        sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));

        if (Span.HasValue)
        {
            sb.AppendLine();
            var text = File.ReadAllText(Span.Value.FilePath);

            var tokenizer = new Tokenizer(Span.Value.FilePath, text);
            tokenizer.Tokenize();

            var lines = text.Split('\n');

            var startLine = Span.Value.Start.Line;
            var endLine = Span.Value.End.Line;

            const int CONTEXT_LINES = 3;

            var contextStartLine = Math.Max(1, startLine - CONTEXT_LINES);
            var contextEndLine = Math.Min(lines.Length, endLine + CONTEXT_LINES);

            var numberPadding = contextEndLine.ToString().Length;

            var codePadding = 0;
            for (var i = contextStartLine - 1; i < contextEndLine && i < lines.Length; i++)
            {
                var lineLength = lines[i].Length;
                if (lineLength > codePadding)
                {
                    codePadding = lineLength;
                }
            }

            sb.Append('╭');
            sb.Append(new string('─', numberPadding + 2));
            sb.Append('┬');
            sb.Append(new string('─', codePadding + 2));
            sb.Append('╮');
            sb.AppendLine();

            for (var i = contextStartLine; i <= contextEndLine; i++)
            {
                var line = lines[i - 1];

                sb.Append("│ ");
                sb.Append(i.ToString().PadRight(numberPadding));
                sb.Append(" │ ");
                sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, tokenizer.Tokens));
                // sb.Append(line.PadRight(codePadding));
                sb.Append(" │");
                sb.AppendLine();

                if (i >= startLine && i <= endLine)
                {
                    var markerStartColumn = 1;
                    var markerEndColumn = line.Length;

                    if (i == startLine)
                    {
                        markerStartColumn = Span.Value.Start.Column;
                    }

                    if (i == endLine)
                    {
                        markerEndColumn = Span.Value.End.Column;
                    }

                    var markerLength = markerEndColumn - markerStartColumn;
                    var marker = new string('^', markerLength);

                    var markerColor = Severity switch
                    {
                        DiagnosticSeverity.Info => ConsoleColors.Blue,
                        DiagnosticSeverity.Warning => ConsoleColors.Yellow,
                        DiagnosticSeverity.Error => ConsoleColors.Red,
                        _ => ConsoleColors.White
                    };

                    sb.Append("│ ");
                    sb.Append(new string(' ', numberPadding));
                    sb.Append(" │ ");
                    sb.Append(new string(' ', markerStartColumn - 1));
                    sb.Append(ConsoleColors.Colorize(marker, markerColor));
                    sb.Append(new string(' ', codePadding - (markerStartColumn - 1) - markerLength));
                    sb.Append(" │");
                    sb.AppendLine();
                }
            }

            sb.Append('╰');
            sb.Append(new string('─', numberPadding + 2));
            sb.Append('┴');
            sb.Append(new string('─', codePadding + 2));
            sb.Append('╯');
        }

        if (Help != null)
        {
            sb.AppendLine();
            sb.Append(ConsoleColors.Colorize($"help: {Help}", ConsoleColors.Cyan));
        }

        return sb.ToString();
    }

    private static string ApplySyntaxHighlighting(string line, int lineNumber, List<Token> tokens)
    {
        var sb = new StringBuilder();
        var lineTokens = tokens
            .Where(t => t.Span.Start.Line == lineNumber)
            .OrderBy(t => t.Span.Start.Column)
            .ToList();

        if (lineTokens.Count == 0)
        {
            return line;
        }

        var currentColumn = 1;

        foreach (var token in lineTokens)
        {
            var tokenStart = token.Span.Start.Column;
            var tokenEnd = token.Span.End.Column;

            if (tokenStart > currentColumn && currentColumn - 1 < line.Length)
            {
                var beforeLength = Math.Min(tokenStart - currentColumn, line.Length - (currentColumn - 1));
                if (beforeLength > 0)
                {
                    var beforeToken = line.Substring(currentColumn - 1, beforeLength);
                    sb.Append(beforeToken);
                }
            }

            var tokenLength = tokenEnd - tokenStart;
            if (tokenStart >= 1 && tokenStart - 1 < line.Length && tokenLength > 0)
            {
                var availableLength = line.Length - (tokenStart - 1);
                var actualLength = Math.Min(tokenLength, availableLength);

                if (actualLength > 0)
                {
                    var tokenText = line.Substring(tokenStart - 1, actualLength);
                    var coloredToken = ColorizeToken(token, tokenText);
                    sb.Append(coloredToken);
                }
            }

            currentColumn = tokenEnd;
        }

        if (currentColumn - 1 < line.Length)
        {
            var remaining = line[(currentColumn - 1)..];
            sb.Append(remaining);
        }

        return sb.ToString();
    }

    private static string ColorizeToken(Token token, string tokenText)
    {
        switch (token)
        {
            case IdentifierToken:
            {
                return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightWhite);
            }
            case StringLiteralToken:
            {
                return ConsoleColors.Colorize(tokenText, ConsoleColors.Green);
            }
            case IntLiteralToken:
            case FloatLiteralToken:
            case BoolLiteralToken:
            {
                return ConsoleColors.Colorize(tokenText, ConsoleColors.Magenta);
            }
            case SymbolToken symbolToken:
            {
                switch (symbolToken.Symbol)
                {
                    case Symbol.Func:
                    case Symbol.Return:
                    case Symbol.If:
                    case Symbol.Else:
                    case Symbol.While:
                    case Symbol.Break:
                    case Symbol.Continue:
                    case Symbol.Struct:
                    case Symbol.Let:
                    case Symbol.Extern:
                    case Symbol.For:
                    case Symbol.In:
                    {
                        return ConsoleColors.Colorize(tokenText, ConsoleColors.Bold + ConsoleColors.Blue);
                    }
                    case Symbol.Assign:
                    case Symbol.Bang:
                    case Symbol.Equal:
                    case Symbol.NotEqual:
                    case Symbol.LessThan:
                    case Symbol.LessThanOrEqual:
                    case Symbol.GreaterThan:
                    case Symbol.GreaterThanOrEqual:
                    case Symbol.Plus:
                    case Symbol.Minus:
                    case Symbol.Star:
                    case Symbol.ForwardSlash:
                    case Symbol.Caret:
                    case Symbol.Ampersand:
                    {
                        return ConsoleColors.Colorize(tokenText, ConsoleColors.Yellow);
                    }
                    case Symbol.Colon:
                    case Symbol.OpenParen:
                    case Symbol.CloseParen:
                    case Symbol.OpenBrace:
                    case Symbol.CloseBrace:
                    case Symbol.OpenBracket:
                    case Symbol.CloseBracket:
                    case Symbol.Comma:
                    case Symbol.Period:
                    case Symbol.Semi:
                    {
                        return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightBlack);
                    }
                }

                break;
            }
        }

        return tokenText;
    }
}

public enum DiagnosticSeverity
{
    Info,
    Warning,
    Error
}

public static class ConsoleColors
{
    public const string Reset = "\e[0m";
    public const string Bold = "\e[1m";
    public const string Faint = "\e[2m";
    public const string Italic = "\e[3m";
    public const string Underline = "\e[4m";
    public const string SlowBlink = "\e[5m";
    public const string RapidBlink = "\e[6m";
    public const string SwapBgAndFg = "\e[7m";
    public const string Conceal = "\e[8m";
    public const string CrossedOut = "\e[9m";

    public const string DefaultFont = "\e[10m";
    public const string AltFont1 = "\e[11m";
    public const string AltFont2 = "\e[12m";
    public const string AltFont3 = "\e[13m";
    public const string AltFont4 = "\e[14m";
    public const string AltFont5 = "\e[15m";
    public const string AltFont6 = "\e[16m";
    public const string AltFont7 = "\e[17m";
    public const string AltFont8 = "\e[18m";
    public const string AltFont9 = "\e[19m";

    public const string Black = "\e[30m";
    public const string Red = "\e[31m";
    public const string Green = "\e[32m";
    public const string Yellow = "\e[33m";
    public const string Blue = "\e[34m";
    public const string Magenta = "\e[35m";
    public const string Cyan = "\e[36m";
    public const string White = "\e[37m";

    public const string BrightBlack = "\e[90m";
    public const string BrightRed = "\e[91m";
    public const string BrightGreen = "\e[92m";
    public const string BrightYellow = "\e[93m";
    public const string BrightBlue = "\e[94m";
    public const string BrightMagenta = "\e[95m";
    public const string BrightCyan = "\e[96m";
    public const string BrightWhite = "\e[97m";

    private static bool IsColorSupported()
    {
        var term = Environment.GetEnvironmentVariable("TERM");
        var colorTerm = Environment.GetEnvironmentVariable("COLORTERM");
        return !string.IsNullOrEmpty(term) || !string.IsNullOrEmpty(colorTerm) || !Console.IsOutputRedirected;
    }

    public static string Colorize(string text, string color)
    {
        return IsColorSupported() ? $"{color}{text}{Reset}" : text;
    }
}