using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace AxCopilot.Services;

public static partial class MarkdownRenderer
{
    // ─── 구문 하이라이팅 ──────────────────────────────────────────────────

    private static readonly Brush KeywordBrush = new SolidColorBrush(Color.FromRgb(0xC5, 0x86, 0xC0));   // purple
    private static readonly Brush TypeBrush = new SolidColorBrush(Color.FromRgb(0x4E, 0xC9, 0xB0));      // teal
    private static readonly Brush StringBrush = new SolidColorBrush(Color.FromRgb(0xCE, 0x91, 0x78));    // orange
    private static readonly Brush CommentBrush = new SolidColorBrush(Color.FromRgb(0x6A, 0x99, 0x55));   // green
    private static readonly Brush NumberBrush = new SolidColorBrush(Color.FromRgb(0xB5, 0xCE, 0xA8));    // light green
    private static readonly Brush MethodBrush = new SolidColorBrush(Color.FromRgb(0xDC, 0xDC, 0xAA));    // yellow
    private static readonly Brush DirectiveBrush = new SolidColorBrush(Color.FromRgb(0x9C, 0xDC, 0xFE)); // light blue

    private static readonly HashSet<string> CSharpKeywords = new(StringComparer.Ordinal)
    {
        "abstract","as","async","await","base","bool","break","byte","case","catch","char","checked",
        "class","const","continue","decimal","default","delegate","do","double","else","enum","event",
        "explicit","extern","false","finally","fixed","float","for","foreach","goto","if","implicit",
        "in","int","interface","internal","is","lock","long","namespace","new","null","object","operator",
        "out","override","params","private","protected","public","readonly","ref","return","sbyte","sealed",
        "short","sizeof","stackalloc","static","string","struct","switch","this","throw","true","try",
        "typeof","uint","ulong","unchecked","unsafe","ushort","using","var","virtual","void","volatile",
        "while","yield","record","init","required","get","set","value","where","when","and","or","not",
    };

    private static readonly HashSet<string> PythonKeywords = new(StringComparer.Ordinal)
    {
        "False","None","True","and","as","assert","async","await","break","class","continue","def",
        "del","elif","else","except","finally","for","from","global","if","import","in","is","lambda",
        "nonlocal","not","or","pass","raise","return","try","while","with","yield",
    };

    private static readonly HashSet<string> JsKeywords = new(StringComparer.Ordinal)
    {
        "abstract","arguments","async","await","boolean","break","byte","case","catch","char","class",
        "const","continue","debugger","default","delete","do","double","else","enum","eval","export",
        "extends","false","final","finally","float","for","from","function","goto","if","implements",
        "import","in","instanceof","int","interface","let","long","native","new","null","of","package",
        "private","protected","public","return","short","static","super","switch","synchronized","this",
        "throw","throws","transient","true","try","typeof","undefined","var","void","volatile","while",
        "with","yield",
    };

    private static readonly HashSet<string> JavaKeywords = new(StringComparer.Ordinal)
    {
        "abstract","assert","boolean","break","byte","case","catch","char","class","const","continue",
        "default","do","double","else","enum","extends","false","final","finally","float","for","goto",
        "if","implements","import","instanceof","int","interface","long","native","new","null","package",
        "private","protected","public","return","short","static","strictfp","super","switch","synchronized",
        "this","throw","throws","transient","true","try","void","volatile","while","var","record","sealed",
        "permits","yield",
    };

    private static readonly HashSet<string> SqlKeywords = new(StringComparer.OrdinalIgnoreCase)
    {
        "SELECT","FROM","WHERE","INSERT","INTO","UPDATE","SET","DELETE","CREATE","DROP","ALTER","TABLE",
        "INDEX","VIEW","JOIN","INNER","LEFT","RIGHT","OUTER","FULL","CROSS","ON","AND","OR","NOT","IN",
        "IS","NULL","LIKE","BETWEEN","EXISTS","HAVING","GROUP","BY","ORDER","ASC","DESC","LIMIT","OFFSET",
        "UNION","ALL","DISTINCT","AS","CASE","WHEN","THEN","ELSE","END","VALUES","PRIMARY","KEY","FOREIGN",
        "REFERENCES","CONSTRAINT","UNIQUE","CHECK","DEFAULT","COUNT","SUM","AVG","MIN","MAX","CAST",
        "COALESCE","IF","BEGIN","COMMIT","ROLLBACK","GRANT","REVOKE","TRUNCATE","WITH","RECURSIVE",
    };

    private static HashSet<string> GetKeywordsForLang(string lang) => lang.ToLowerInvariant() switch
    {
        "csharp" or "cs" or "c#" => CSharpKeywords,
        "python" or "py" => PythonKeywords,
        "javascript" or "js" or "jsx" => JsKeywords,
        "typescript" or "ts" or "tsx" => JsKeywords,
        "java" or "kotlin" or "kt" => JavaKeywords,
        "sql" or "mysql" or "postgresql" or "sqlite" => SqlKeywords,
        "go" or "golang" => new(StringComparer.Ordinal)
        {
            "break","case","chan","const","continue","default","defer","else","fallthrough","for","func",
            "go","goto","if","import","interface","map","package","range","return","select","struct",
            "switch","type","var","nil","true","false","append","len","cap","make","new","panic","recover",
        },
        "rust" or "rs" => new(StringComparer.Ordinal)
        {
            "as","async","await","break","const","continue","crate","dyn","else","enum","extern","false",
            "fn","for","if","impl","in","let","loop","match","mod","move","mut","pub","ref","return",
            "self","Self","static","struct","super","trait","true","type","unsafe","use","where","while",
            "yield","Box","Vec","String","Option","Result","Some","None","Ok","Err","println","macro_rules",
        },
        "cpp" or "c++" or "c" or "h" or "hpp" => new(StringComparer.Ordinal)
        {
            "auto","break","case","char","const","continue","default","do","double","else","enum","extern",
            "float","for","goto","if","inline","int","long","register","return","short","signed","sizeof",
            "static","struct","switch","typedef","union","unsigned","void","volatile","while","class",
            "namespace","using","public","private","protected","virtual","override","template","typename",
            "nullptr","true","false","new","delete","throw","try","catch","const_cast","dynamic_cast",
            "static_cast","reinterpret_cast","bool","string","include","define","ifdef","ifndef","endif",
        },
        _ => CSharpKeywords, // default
    };

    private static readonly Regex SyntaxPattern = new(
        @"(//[^\n]*|#[^\n]*)" +                           // single-line comment (// or #)
        @"|(""(?:[^""\\]|\\.)*""|'(?:[^'\\]|\\.)*')" +    // strings
        @"|(\b\d+\.?\d*[fFdDmMlL]?\b)" +                 // numbers
        @"|(\b[A-Z]\w*(?=\s*[\.<\(]))" +                  // type-like (PascalCase before . < ()
        @"|(\b\w+(?=\s*\())" +                            // method call
        @"|(\b\w+\b)",                                    // identifier / keyword
        RegexOptions.Compiled);

    private static void ApplySyntaxHighlighting(TextBlock tb, string code, string lang, Brush defaultColor)
    {
        if (string.IsNullOrEmpty(lang) || string.IsNullOrEmpty(code))
        {
            tb.Text = code;
            return;
        }

        var keywords = GetKeywordsForLang(lang);
        var isCommentHash = lang.ToLowerInvariant() is "python" or "py" or "bash" or "sh" or "shell"
                            or "ruby" or "rb" or "yaml" or "yml" or "toml" or "powershell" or "ps1";

        foreach (Match m in SyntaxPattern.Matches(code))
        {
            if (m.Groups[1].Success) // comment
            {
                var commentText = m.Groups[1].Value;
                // # 주석은 해당 언어에서만 적용
                if (commentText.StartsWith('#') && !isCommentHash)
                {
                    // # 이 주석이 아닌 언어: 일반 텍스트로 처리
                    tb.Inlines.Add(new Run(commentText) { Foreground = defaultColor });
                }
                else
                {
                    tb.Inlines.Add(new Run(commentText) { Foreground = CommentBrush, FontStyle = FontStyles.Italic });
                }
            }
            else if (m.Groups[2].Success) // string
            {
                tb.Inlines.Add(new Run(m.Groups[2].Value) { Foreground = StringBrush });
            }
            else if (m.Groups[3].Success) // number
            {
                tb.Inlines.Add(new Run(m.Groups[3].Value) { Foreground = NumberBrush });
            }
            else if (m.Groups[4].Success) // type
            {
                var word = m.Groups[4].Value;
                tb.Inlines.Add(new Run(word) { Foreground = keywords.Contains(word) ? KeywordBrush : TypeBrush });
            }
            else if (m.Groups[5].Success) // method
            {
                var word = m.Groups[5].Value;
                if (keywords.Contains(word))
                    tb.Inlines.Add(new Run(word) { Foreground = KeywordBrush });
                else
                    tb.Inlines.Add(new Run(word) { Foreground = MethodBrush });
            }
            else if (m.Groups[6].Success) // identifier
            {
                var word = m.Groups[6].Value;
                if (keywords.Contains(word))
                    tb.Inlines.Add(new Run(word) { Foreground = KeywordBrush });
                else
                    tb.Inlines.Add(new Run(word) { Foreground = defaultColor });
            }
        }

        // Regex가 매치하지 못한 나머지 문자(공백, 기호 등)를 채움
        // Inlines로 빌드했으므로 원본과 비교하여 누락된 부분 보완
        // 대신 각 매치 사이의 간격을 처리하기 위해 인덱스 기반 접근
        tb.Inlines.Clear();
        int lastEnd = 0;
        foreach (Match m in SyntaxPattern.Matches(code))
        {
            if (m.Index > lastEnd)
                tb.Inlines.Add(new Run(code[lastEnd..m.Index]) { Foreground = defaultColor });

            if (m.Groups[1].Success)
            {
                var commentText = m.Groups[1].Value;
                if (commentText.StartsWith('#') && !isCommentHash)
                    tb.Inlines.Add(new Run(commentText) { Foreground = defaultColor });
                else
                    tb.Inlines.Add(new Run(commentText) { Foreground = CommentBrush, FontStyle = FontStyles.Italic });
            }
            else if (m.Groups[2].Success)
                tb.Inlines.Add(new Run(m.Groups[2].Value) { Foreground = StringBrush });
            else if (m.Groups[3].Success)
                tb.Inlines.Add(new Run(m.Groups[3].Value) { Foreground = NumberBrush });
            else if (m.Groups[4].Success)
            {
                var word = m.Groups[4].Value;
                tb.Inlines.Add(new Run(word) { Foreground = keywords.Contains(word) ? KeywordBrush : TypeBrush });
            }
            else if (m.Groups[5].Success)
            {
                var word = m.Groups[5].Value;
                tb.Inlines.Add(new Run(word) { Foreground = keywords.Contains(word) ? KeywordBrush : MethodBrush });
            }
            else if (m.Groups[6].Success)
            {
                var word = m.Groups[6].Value;
                tb.Inlines.Add(new Run(word) { Foreground = keywords.Contains(word) ? KeywordBrush : defaultColor });
            }

            lastEnd = m.Index + m.Length;
        }
        if (lastEnd < code.Length)
            tb.Inlines.Add(new Run(code[lastEnd..]) { Foreground = defaultColor });
    }
}
