using System.Text;
using System.Windows;
using System.Xml;
using System.Xml.XPath;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;

namespace AxCopilot.Handlers;

/// <summary>
/// L10-1: XML 포맷터·검증기·XPath 쿼리 핸들러. "xml" 프리픽스로 사용합니다.
///
/// 예: xml                    → 클립보드의 XML 자동 포맷
///     xml <root><a>1</a>     → 인라인 XML 포맷
///     xml compact            → 클립보드 XML 압축 (공백 제거)
///     xml xpath //a          → 클립보드 XML에 XPath 쿼리
///     xml validate           → XML 유효성 검증
///     xml attr               → XML 속성 목록 추출
///     xml minify             → compact 와 동일
/// Enter → 결과를 클립보드에 복사.
/// </summary>
public class XmlHandler : IActionHandler
{
    public string? Prefix => "xml";

    public PluginMetadata Metadata => new(
        "XML",
        "XML 포맷터 · 압축 · 검증 · XPath 쿼리",
        "1.0",
        "AX");

    public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
    {
        var q     = query.Trim();
        var items = new List<LauncherItem>();

        if (string.IsNullOrWhiteSpace(q))
        {
            var clip = GetClipboard();
            if (!string.IsNullOrWhiteSpace(clip) && clip.TrimStart().StartsWith('<'))
            {
                // 클립보드에 XML이 있으면 즉시 포맷 미리보기
                items.AddRange(BuildXmlItems(clip, "클립보드"));
            }
            else
            {
                items.Add(new LauncherItem("XML 도구",
                    "예: xml <root>…  /  xml compact  /  xml xpath //path  /  xml validate",
                    null, null, Symbol: "\uE943"));
                items.Add(new LauncherItem("xml compact",   "XML 압축 (공백 제거)",  null, null, Symbol: "\uE943"));
                items.Add(new LauncherItem("xml validate",  "XML 유효성 검증",       null, null, Symbol: "\uE943"));
                items.Add(new LauncherItem("xml xpath //",  "XPath 쿼리",            null, null, Symbol: "\uE943"));
                items.Add(new LauncherItem("xml attr",      "속성 목록 추출",         null, null, Symbol: "\uE943"));
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        var parts = q.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
        var sub   = parts[0].ToLowerInvariant();

        switch (sub)
        {
            case "compact":
            case "minify":
            {
                var src = GetClipboard();
                if (TryMinify(src, out var mini))
                    items.Add(new LauncherItem(mini.Length > 80 ? mini[..80] + "…" : mini,
                        $"압축됨  {src.Length} → {mini.Length} 글자  · Enter 복사",
                        null, ("copy", mini), Symbol: "\uE943"));
                else
                    items.Add(new LauncherItem("XML 파싱 오류", "클립보드에 유효한 XML이 없습니다", null, null, Symbol: "\uE783"));
                break;
            }
            case "validate":
            {
                var src = GetClipboard();
                if (string.IsNullOrWhiteSpace(src))
                {
                    items.Add(new LauncherItem("클립보드 비어 있음", "XML을 클립보드에 복사 후 시도하세요", null, null, Symbol: "\uE783"));
                    break;
                }
                var (ok, err) = ValidateXml(src);
                items.Add(ok
                    ? new LauncherItem("✔ 유효한 XML", $"{src.Length:N0}자  · 잘 형식화된 XML입니다", null, null, Symbol: "\uE73E")
                    : new LauncherItem("✘ XML 오류",   err ?? "알 수 없는 오류", null, null, Symbol: "\uE783"));
                break;
            }
            case "xpath":
            {
                var xpathExpr = parts.Length > 1 ? parts[1].Trim() : "";
                if (string.IsNullOrWhiteSpace(xpathExpr))
                {
                    items.Add(new LauncherItem("XPath 식을 입력하세요", "예: xml xpath //item/title", null, null, Symbol: "\uE783"));
                    break;
                }
                var src = GetClipboard();
                items.AddRange(RunXPath(src, xpathExpr));
                break;
            }
            case "attr":
            {
                var src = GetClipboard();
                items.AddRange(ExtractAttributes(src));
                break;
            }
            default:
            {
                // 인라인 XML 입력 또는 전체 포맷
                var xmlSrc = q.TrimStart().StartsWith('<') ? q : GetClipboard();
                items.AddRange(BuildXmlItems(xmlSrc, q.TrimStart().StartsWith('<') ? "입력" : "클립보드"));
                break;
            }
        }

        return Task.FromResult<IEnumerable<LauncherItem>>(items);
    }

    public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
    {
        if (item.Data is ("copy", string text))
        {
            try
            {
                System.Windows.Application.Current.Dispatcher.Invoke(
                    () => Clipboard.SetText(text));
                NotificationService.Notify("XML", "클립보드에 복사했습니다.");
            }
            catch { /* 비핵심 */ }
        }
        return Task.CompletedTask;
    }

    // ── 빌더 ─────────────────────────────────────────────────────────────────

    private static IEnumerable<LauncherItem> BuildXmlItems(string xml, string source)
    {
        if (string.IsNullOrWhiteSpace(xml))
        {
            yield return new LauncherItem("XML 없음", "클립보드에 XML을 복사해주세요", null, null, Symbol: "\uE783");
            yield break;
        }

        if (TryFormat(xml, out var formatted))
        {
            var preview = formatted.Length > 100 ? formatted[..100].Replace("\n", " ") + "…" : formatted.Replace("\n", " ");
            yield return new LauncherItem(
                $"포맷된 XML ({source})",
                preview,
                null,
                ("copy", formatted),
                Symbol: "\uE943");

            // 루트 요소 이름
            if (TryGetRootElement(xml, out var root))
                yield return new LauncherItem("루트 요소", $"<{root}>", null, ("copy", root), Symbol: "\uE943");

            // 요소 수
            var elemCount = CountElements(xml);
            yield return new LauncherItem("요소 수", $"{elemCount:N0}개 요소", null, null, Symbol: "\uE943");

            // 압축 버전
            if (TryMinify(xml, out var mini))
                yield return new LauncherItem(
                    "압축 XML",
                    $"{xml.Length:N0} → {mini.Length:N0} 글자",
                    null,
                    ("copy", mini),
                    Symbol: "\uE943");
        }
        else
        {
            yield return new LauncherItem("XML 파싱 오류", "유효한 XML을 입력해주세요", null, null, Symbol: "\uE783");
        }
    }

    private static IEnumerable<LauncherItem> RunXPath(string xml, string xpath)
    {
        if (string.IsNullOrWhiteSpace(xml))
            return [new LauncherItem("XML 없음", "클립보드에 XML을 복사해주세요", null, null, Symbol: "\uE783")];

        XPathDocument doc;
        try { doc = new XPathDocument(new System.IO.StringReader(xml)); }
        catch (Exception ex)
        {
            return [new LauncherItem("XML 파싱 오류", ex.Message, null, null, Symbol: "\uE783")];
        }

        XPathNodeIterator iter;
        try
        {
            var nav = doc.CreateNavigator();
            iter = nav.Select(xpath);
        }
        catch (XPathException ex)
        {
            return [new LauncherItem("XPath 오류", ex.Message, null, null, Symbol: "\uE783")];
        }

        var results = new List<string>();
        while (iter.MoveNext() && results.Count < 20)
            results.Add(iter.Current!.OuterXml);

        if (results.Count == 0)
            return [new LauncherItem("결과 없음", $"XPath '{xpath}' — 일치하는 노드 없음", null, null, Symbol: "\uE946")];

        var items  = new List<LauncherItem>();
        var joined = string.Join("\n", results);
        items.Add(new LauncherItem(
            $"XPath 결과  {results.Count}개",
            results[0].Length > 80 ? results[0][..80] + "…" : results[0],
            null, ("copy", joined), Symbol: "\uE943"));

        foreach (var r in results.Take(10))
        {
            var disp = r.Length > 80 ? r[..80] + "…" : r;
            items.Add(new LauncherItem(disp, "Enter 복사", null, ("copy", r), Symbol: "\uE943"));
        }
        return items;
    }

    private static IEnumerable<LauncherItem> ExtractAttributes(string xml)
    {
        if (string.IsNullOrWhiteSpace(xml))
            return [new LauncherItem("XML 없음", "클립보드에 XML을 복사해주세요", null, null, Symbol: "\uE783")];

        try
        {
            var doc = new XmlDocument();
            doc.LoadXml(xml);
            var attrs = new List<(string Element, string Attr, string Value)>();

            foreach (XmlNode node in doc.SelectNodes("//*")!)
            {
                if (node.Attributes == null) continue;
                foreach (XmlAttribute attr in node.Attributes)
                    attrs.Add((node.Name, attr.Name, attr.Value));
            }

            if (attrs.Count == 0)
                return [new LauncherItem("속성 없음", "XML에 속성(attribute)이 없습니다", null, null, Symbol: "\uE946")];

            var items = new List<LauncherItem>
            {
                new($"속성 {attrs.Count}개", "전체 복사: Enter", null,
                    ("copy", string.Join("\n", attrs.Select(a => $"{a.Element}@{a.Attr}={a.Value}"))),
                    Symbol: "\uE943"),
            };
            items.AddRange(attrs.Take(15).Select(a =>
                new LauncherItem($"{a.Element}@{a.Attr}", a.Value, null, ("copy", a.Value), Symbol: "\uE943")));
            return items;
        }
        catch (Exception ex)
        {
            return [new LauncherItem("XML 오류", ex.Message, null, null, Symbol: "\uE783")];
        }
    }

    // ── XML 헬퍼 ─────────────────────────────────────────────────────────────

    private static bool TryFormat(string xml, out string result)
    {
        result = "";
        try
        {
            var doc = new XmlDocument();
            doc.LoadXml(xml);
            var sb  = new StringBuilder();
            var st  = new XmlWriterSettings { Indent = true, IndentChars = "  ", NewLineChars = "\n" };
            using var writer = XmlWriter.Create(sb, st);
            doc.WriteTo(writer);
            writer.Flush();
            result = sb.ToString();
            return true;
        }
        catch { return false; }
    }

    private static bool TryMinify(string xml, out string result)
    {
        result = "";
        try
        {
            var doc = new XmlDocument();
            doc.LoadXml(xml);
            var sb  = new StringBuilder();
            var st  = new XmlWriterSettings { Indent = false, NewLineHandling = NewLineHandling.None };
            using var writer = XmlWriter.Create(sb, st);
            doc.WriteTo(writer);
            writer.Flush();
            result = sb.ToString();
            return true;
        }
        catch { return false; }
    }

    private static (bool Ok, string? Error) ValidateXml(string xml)
    {
        try
        {
            var doc = new XmlDocument();
            doc.LoadXml(xml);
            return (true, null);
        }
        catch (XmlException ex)
        {
            return (false, $"줄 {ex.LineNumber}, 열 {ex.LinePosition}: {ex.Message}");
        }
    }

    private static bool TryGetRootElement(string xml, out string root)
    {
        root = "";
        try
        {
            var doc = new XmlDocument();
            doc.LoadXml(xml);
            root = doc.DocumentElement?.Name ?? "";
            return !string.IsNullOrEmpty(root);
        }
        catch { return false; }
    }

    private static int CountElements(string xml)
    {
        try
        {
            var doc = new XmlDocument();
            doc.LoadXml(xml);
            return doc.SelectNodes("//*")?.Count ?? 0;
        }
        catch { return 0; }
    }

    private static string GetClipboard()
    {
        try
        {
            return System.Windows.Application.Current.Dispatcher.Invoke(
                () => Clipboard.ContainsText() ? Clipboard.GetText() : "");
        }
        catch { return ""; }
    }
}
