using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Windows;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;

namespace AxCopilot.Handlers;

/// <summary>
/// L20-3: 문자열 조작 도구 핸들러. "str" 프리픽스로 사용합니다.
///
/// 예: str                    → 클립보드 텍스트 조작 메뉴
///     str escape html        → HTML 특수문자 이스케이프
///     str unescape html      → HTML 이스케이프 해제
///     str escape url         → URL 인코딩 (퍼센트)
///     str unescape url       → URL 디코딩
///     str escape json        → JSON 문자열 이스케이프
///     str escape regex       → 정규식 이스케이프
///     str repeat 3           → 클립보드 텍스트 3회 반복
///     str repeat 5 ,         → 쉼표 구분 5회 반복
///     str pad 20             → 20자 우측 공백 패딩
///     str pad 20 left        → 좌측 패딩
///     str pad 20 * right     → 지정 문자로 우측 패딩
///     str wrap 80            → 80자 줄바꿈
///     str lines              → 줄 수·단어·문자 통계
///     str sort               → 줄 정렬 (오름차순)
///     str sort desc          → 줄 정렬 (내림차순)
///     str unique             → 중복 줄 제거
///     str join ,             → 여러 줄 → 쉼표 구분 한 줄
///     str split ,            → 쉼표 구분 → 여러 줄
///     str replace a b        → 텍스트 내 a를 b로 교체
///     str extract email      → 이메일 주소 추출
///     str extract url        → URL 추출
///     str extract number     → 숫자 추출
/// Enter → 결과 복사.
/// </summary>
public partial class StrHandler : IActionHandler
{
    public string? Prefix => "str";

    public PluginMetadata Metadata => new(
        "Str",
        "문자열 조작 도구 — HTML/URL/JSON 이스케이프·반복·패딩·줄 정렬·추출",
        "1.0",
        "AX");

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

        // 클립보드 읽기
        string? clipboard = null;
        try
        {
            System.Windows.Application.Current.Dispatcher.Invoke(() =>
            {
                if (Clipboard.ContainsText()) clipboard = Clipboard.GetText();
            });
        }
        catch { }

        if (string.IsNullOrWhiteSpace(q))
        {
            items.Add(new LauncherItem("문자열 조작 도구",
                "str escape/unescape  /  str repeat  /  str pad  /  str sort  /  str extract  …",
                null, null, Symbol: "\uE8AB"));
            if (!string.IsNullOrWhiteSpace(clipboard))
            {
                var preview = clipboard!.Length > 40 ? clipboard[..40] + "…" : clipboard;
                items.Add(new LauncherItem($"클립보드: \"{preview}\"",
                    $"{clipboard.Length}자  · 아래 서브커맨드로 조작", null, null, Symbol: "\uE8AB"));
                BuildQuickMenu(items, clipboard!);
            }
            else
            {
                items.Add(new LauncherItem("클립보드가 비어 있습니다", "텍스트를 복사한 뒤 사용하세요",
                    null, null, Symbol: "\uE946"));
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        var sub   = parts[0].ToLowerInvariant();
        var text  = parts.Length > 1
            ? string.Join(" ", parts[1..])  // 인라인 텍스트 (없으면 클립보드)
            : clipboard ?? "";

        // escape / unescape
        if (sub is "escape" or "esc" or "unescape" or "unesc")
        {
            var isEscape = sub is "escape" or "esc";
            var target   = parts.Length >= 2 ? parts[1].ToLowerInvariant() : "html";
            var src      = parts.Length >= 3 ? string.Join(" ", parts[2..]) : clipboard ?? "";

            if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("처리할 텍스트가 없습니다")); }
            else
            {
                var (html, url, json, reg) = (
                    isEscape ? HtmlEncode(src)        : HtmlDecode(src),
                    isEscape ? Uri.EscapeDataString(src) : Uri.UnescapeDataString(src),
                    isEscape ? JsonEscape(src)        : JsonUnescape(src),
                    isEscape ? RegexEscape(src)       : src
                );
                var label = isEscape ? "이스케이프" : "이스케이프 해제";
                items.Add(new LauncherItem($"── {label} ──", "", null, null, Symbol: "\uE8AB"));
                items.Add(CopyItem("HTML",  isEscape ? HtmlEncode(src) : HtmlDecode(src)));
                items.Add(CopyItem("URL",   isEscape ? Uri.EscapeDataString(src) : Uri.UnescapeDataString(src)));
                items.Add(CopyItem("JSON",  isEscape ? JsonEscape(src) : JsonUnescape(src)));
                if (isEscape) items.Add(CopyItem("Regex", RegexEscape(src)));
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // repeat
        if (sub == "repeat")
        {
            int count = 3;
            string sep = "";
            string src = clipboard ?? "";

            if (parts.Length >= 2 && int.TryParse(parts[1], out var n)) count = Math.Clamp(n, 1, 100);
            if (parts.Length >= 3) sep = parts[2] == "\\n" ? "\n" : parts[2] == "\\t" ? "\t" : parts[2];
            if (parts.Length >= 4) src = string.Join(" ", parts[3..]);

            if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("반복할 텍스트가 없습니다")); }
            else
            {
                var result = string.Join(sep, Enumerable.Repeat(src, count));
                items.Add(new LauncherItem(result.Length > 60 ? result[..60] + "…" : result,
                    $"{count}회 반복  · Enter 복사", null, ("copy", result), Symbol: "\uE8AB"));
                items.Add(CopyItem("결과", result));
                items.Add(CopyItem("결과 길이", $"{result.Length}자"));
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // pad
        if (sub == "pad")
        {
            if (parts.Length < 2 || !int.TryParse(parts[1], out var width))
            { items.Add(ErrorItem("예: str pad 20  /  str pad 20 left  /  str pad 20 * right")); }
            else
            {
                var side    = parts.Length >= 3 ? parts[2].ToLowerInvariant() : "right";
                var padChar = ' ';
                if (parts.Length >= 4 && parts[2].Length == 1) { padChar = parts[2][0]; side = parts[3].ToLowerInvariant(); }
                if (parts.Length >= 3 && parts[2].Length == 1 && !"left right both".Contains(parts[2])) padChar = parts[2][0];

                var src = clipboard ?? "";
                if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("클립보드에 텍스트가 없습니다")); }
                else
                {
                    var result = side switch
                    {
                        "left"  => src.PadLeft(width, padChar),
                        "both"  => src.PadLeft((src.Length + width) / 2, padChar).PadRight(width, padChar),
                        _       => src.PadRight(width, padChar)
                    };
                    items.Add(new LauncherItem($"\"{result}\"", $"{side} 패딩 {width}자  · Enter 복사",
                        null, ("copy", result), Symbol: "\uE8AB"));
                    items.Add(CopyItem("결과", result));
                }
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // wrap
        if (sub == "wrap")
        {
            if (parts.Length < 2 || !int.TryParse(parts[1], out var cols))
            { items.Add(ErrorItem("예: str wrap 80")); }
            else
            {
                var src = parts.Length >= 3 ? string.Join(" ", parts[2..]) : clipboard ?? "";
                if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("줄바꿈할 텍스트가 없습니다")); }
                else
                {
                    var result = WordWrap(src, cols);
                    var preview = result.Split('\n').Take(5);
                    items.Add(new LauncherItem($"{cols}자 줄바꿈",
                        $"{result.Split('\n').Length}줄  · Enter 복사", null, ("copy", result), Symbol: "\uE8AB"));
                    foreach (var line in preview)
                        items.Add(new LauncherItem(line.Length > 60 ? line[..60] + "…" : line, "",
                            null, null, Symbol: "\uE8AB"));
                }
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // sort
        if (sub == "sort")
        {
            var src  = parts.Length >= 3 ? string.Join(" ", parts[2..]) : clipboard ?? "";
            var desc = parts.Length >= 2 && parts[1].ToLowerInvariant() is "desc" or "d";
            if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("정렬할 텍스트가 없습니다")); }
            else
            {
                var lines  = src.Split('\n', StringSplitOptions.RemoveEmptyEntries);
                var sorted = desc ? lines.OrderByDescending(l => l).ToArray()
                                  : lines.OrderBy(l => l).ToArray();
                var result = string.Join("\n", sorted);
                items.Add(new LauncherItem($"{sorted.Length}줄 {(desc ? "내림차순" : "오름차순")} 정렬",
                    "Enter 복사", null, ("copy", result), Symbol: "\uE8AB"));
                foreach (var line in sorted.Take(6))
                    items.Add(new LauncherItem(line.Length > 60 ? line[..60] : line, "",
                        null, null, Symbol: "\uE8AB"));
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // unique
        if (sub is "unique" or "dedup")
        {
            var src = parts.Length >= 2 ? string.Join(" ", parts[1..]) : clipboard ?? "";
            if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("중복 제거할 텍스트가 없습니다")); }
            else
            {
                var lines  = src.Split('\n', StringSplitOptions.RemoveEmptyEntries);
                var unique = lines.Distinct(StringComparer.Ordinal).ToArray();
                var result = string.Join("\n", unique);
                items.Add(new LauncherItem($"{lines.Length}줄 → {unique.Length}줄 (중복 {lines.Length - unique.Length}개 제거)",
                    "Enter 복사", null, ("copy", result), Symbol: "\uE8AB"));
                foreach (var line in unique.Take(6))
                    items.Add(new LauncherItem(line.Length > 60 ? line[..60] : line, "",
                        null, null, Symbol: "\uE8AB"));
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // join
        if (sub == "join")
        {
            var sep = parts.Length >= 2 ? parts[1] : ",";
            if (sep == "\\n") sep = "\n"; else if (sep == "\\t") sep = "\t";
            var src = clipboard ?? "";
            if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("연결할 텍스트가 없습니다")); }
            else
            {
                var lines  = src.Split('\n', StringSplitOptions.RemoveEmptyEntries);
                var result = string.Join(sep, lines);
                items.Add(new LauncherItem(result.Length > 60 ? result[..60] + "…" : result,
                    $"{lines.Length}줄 연결  · Enter 복사", null, ("copy", result), Symbol: "\uE8AB"));
                items.Add(CopyItem("결과", result));
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // split
        if (sub == "split")
        {
            var sep  = parts.Length >= 2 ? parts[1] : ",";
            if (sep == "\\n") sep = "\n"; else if (sep == "\\t") sep = "\t";
            var src  = parts.Length >= 3 ? string.Join(" ", parts[2..]) : clipboard ?? "";
            if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("분리할 텍스트가 없습니다")); }
            else
            {
                var splitted = src.Split(sep, StringSplitOptions.None);
                var result   = string.Join("\n", splitted);
                items.Add(new LauncherItem($"'{sep}'로 분리 → {splitted.Length}개",
                    "Enter 복사", null, ("copy", result), Symbol: "\uE8AB"));
                foreach (var item in splitted.Take(8))
                    items.Add(new LauncherItem(item.Length > 60 ? item[..60] : item, "",
                        null, null, Symbol: "\uE8AB"));
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // replace
        if (sub == "replace")
        {
            if (parts.Length < 3) { items.Add(ErrorItem("예: str replace 찾을텍스트 바꿀텍스트")); }
            else
            {
                var from = parts[1];
                var to   = parts[2];
                var src  = parts.Length >= 4 ? string.Join(" ", parts[3..]) : clipboard ?? "";
                if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("처리할 텍스트가 없습니다")); }
                else
                {
                    var count  = 0;
                    var result = ReplaceCount(src, from, to, out count);
                    items.Add(new LauncherItem($"'{from}' → '{to}'  ({count}개 교체)",
                        "Enter 복사", null, ("copy", result), Symbol: "\uE8AB"));
                    items.Add(CopyItem("결과", result));
                }
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // extract
        if (sub == "extract")
        {
            var target = parts.Length >= 2 ? parts[1].ToLowerInvariant() : "url";
            var src    = parts.Length >= 3 ? string.Join(" ", parts[2..]) : clipboard ?? "";
            if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("추출할 텍스트가 없습니다")); }
            else
            {
                var matches = target switch
                {
                    "email" => EmailRegex().Matches(src).Select(m => m.Value).Distinct().ToList(),
                    "url"   => UrlRegex().Matches(src).Select(m => m.Value).Distinct().ToList(),
                    "num" or "number" or "숫자" =>
                        NumberRegex().Matches(src).Select(m => m.Value).ToList(),
                    "ip"    => IpRegex().Matches(src).Select(m => m.Value).Distinct().ToList(),
                    _       => new List<string>()
                };
                if (matches.Count == 0)
                    items.Add(new LauncherItem($"'{target}' 패턴 없음", src.Length > 40 ? src[..40] : src,
                        null, null, Symbol: "\uE8AB"));
                else
                {
                    items.Add(new LauncherItem($"{target} {matches.Count}개 추출",
                        "Enter로 전체 복사", null, ("copy", string.Join("\n", matches)), Symbol: "\uE8AB"));
                    foreach (var m in matches.Take(10))
                        items.Add(new LauncherItem(m, "", null, ("copy", m), Symbol: "\uE8AB"));
                }
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // lines
        if (sub is "lines" or "info" or "count")
        {
            var src = clipboard ?? "";
            if (string.IsNullOrEmpty(src)) { items.Add(ErrorItem("분석할 텍스트가 없습니다")); }
            else
            {
                var lineArr = src.Split('\n');
                var words   = src.Split(new[] {' ','\t','\n','\r'}, StringSplitOptions.RemoveEmptyEntries);
                items.Add(new LauncherItem($"{lineArr.Length}줄  ·  {words.Length}단어  ·  {src.Length}자",
                    "텍스트 분석", null, null, Symbol: "\uE8AB"));
                items.Add(CopyItem("전체 줄 수",    lineArr.Length.ToString()));
                items.Add(CopyItem("빈 줄 수",      lineArr.Count(l => string.IsNullOrWhiteSpace(l)).ToString()));
                items.Add(CopyItem("단어 수",        words.Length.ToString()));
                items.Add(CopyItem("전체 문자 수",   src.Length.ToString()));
                items.Add(CopyItem("공백 제외 문자", src.Count(c => !char.IsWhiteSpace(c)).ToString()));
                items.Add(CopyItem("바이트 (UTF-8)", Encoding.UTF8.GetByteCount(src).ToString()));
            }
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        // 알 수 없는 커맨드
        items.Add(new LauncherItem($"알 수 없는 서브커맨드: '{sub}'",
            "escape · unescape · repeat · pad · wrap · sort · unique · join · split · replace · extract · lines",
            null, null, Symbol: "\uE783"));
        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("Str", "클립보드에 복사했습니다.");
            }
            catch { }
        }
        return Task.CompletedTask;
    }

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

    private static void BuildQuickMenu(List<LauncherItem> items, string src)
    {
        items.Add(CopyItem("HTML 이스케이프", HtmlEncode(src)));
        items.Add(CopyItem("URL 인코딩",    Uri.EscapeDataString(src)));
        items.Add(CopyItem("JSON 이스케이프", JsonEscape(src)));
    }

    private static string HtmlEncode(string s) => s
        .Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;")
        .Replace("\"", "&quot;").Replace("'", "&#39;");

    private static string HtmlDecode(string s) =>
        HttpUtility.HtmlDecode(s);

    private static string JsonEscape(string s)
    {
        var sb = new StringBuilder();
        foreach (var c in s)
        {
            switch (c)
            {
                case '"':  sb.Append("\\\""); break;
                case '\\': sb.Append("\\\\"); break;
                case '\n': sb.Append("\\n");  break;
                case '\r': sb.Append("\\r");  break;
                case '\t': sb.Append("\\t");  break;
                default:
                    if (c < 0x20) sb.Append($"\\u{(int)c:X4}");
                    else          sb.Append(c);
                    break;
            }
        }
        return sb.ToString();
    }

    private static string JsonUnescape(string s) =>
        s.Replace("\\\"", "\"").Replace("\\\\", "\\")
         .Replace("\\n", "\n").Replace("\\r", "\r").Replace("\\t", "\t");

    private static string RegexEscape(string s) => Regex.Escape(s);

    private static string WordWrap(string text, int cols)
    {
        var words  = text.Split(' ');
        var sb     = new StringBuilder();
        int colPos = 0;
        foreach (var word in words)
        {
            if (colPos + word.Length + 1 > cols && colPos > 0) { sb.Append('\n'); colPos = 0; }
            else if (colPos > 0)                              { sb.Append(' ');  colPos++; }
            sb.Append(word);
            colPos += word.Length;
        }
        return sb.ToString();
    }

    private static string ReplaceCount(string src, string from, string to, out int count)
    {
        count = 0;
        var sb  = new StringBuilder();
        int pos = 0;
        while (true)
        {
            var idx = src.IndexOf(from, pos, StringComparison.Ordinal);
            if (idx < 0) { sb.Append(src[pos..]); break; }
            sb.Append(src[pos..idx]);
            sb.Append(to);
            count++;
            pos = idx + from.Length;
        }
        return sb.ToString();
    }

    private static LauncherItem CopyItem(string label, string value) =>
        new(label, value, null, ("copy", value), Symbol: "\uE8AB");

    private static LauncherItem ErrorItem(string msg) =>
        new(msg, "올바른 입력 형식을 확인하세요", null, null, Symbol: "\uE783");

    [GeneratedRegex(@"[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}")]
    private static partial Regex EmailRegex();

    [GeneratedRegex(@"https?://[^\s""'<>]+")]
    private static partial Regex UrlRegex();

    [GeneratedRegex(@"-?\d+(\.\d+)?")]
    private static partial Regex NumberRegex();

    [GeneratedRegex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b")]
    private static partial Regex IpRegex();
}
