using System.Text.Json;
using System.Text.RegularExpressions;
using System.Globalization;
using YMhut.Box.Core.Api;

namespace YMhut.Box.Core.Tools;

public static class ToolResultBuilder
{
    public static ToolResultDocument FromOutput(IToolModule module, string output, string language = "zh-CN")
    {
        var spec = ToolPageSpecCatalog.For(module);
        var lines = Lines(output);
        var blocks = BuildBlocks(module, spec, output, lines, language);
        if (blocks.Count == 0)
        {
            blocks = ToolResultDocument.FromOutput(module, output).Blocks;
        }

        return Document(module.Id, spec.Result, output, blocks, language: language);
    }

    public static ToolResultDocument FromRemote(ApiEndpoint endpoint, ApiResponse response, string output, string language = "zh-CN")
    {
        var lines = Lines(output);
        var displayLines = RemoveSourceHeader(lines);
        var blocks = new List<ToolResultBlock>();
        blocks.AddRange(BuildRemoteBlocks(endpoint.Id, displayLines, language));
        if (blocks.Count == 0)
        {
            blocks.AddRange(GenericTextBlocks(displayLines.Count > 0 ? displayLines : lines, language));
        }

        blocks.Add(ToolResultBlock.KeyValue(
            T(language, "来源与隐私", "Source and privacy"),
            [
                Pair(T(language, "数据源", "Data source"), endpoint.SourceName),
                Pair(T(language, "来源类型", "Source type"), SourceTypeLabel(endpoint, language)),
                Pair(T(language, "获取时间", "Fetched at"), response.FetchedAt.ToString("yyyy-MM-dd HH:mm:ss zzz"))
            ],
            SourceMetadata(endpoint)));

        return new ToolResultDocument(
            endpoint.Id,
            GuessRemoteResultKind(endpoint.Id),
            output,
            blocks,
            RemoteMetadata(endpoint, GuessRemoteResultKind(endpoint.Id), language),
            output,
            endpoint.SourceName,
            response.Success ? "ok" : "error");
    }

    private static IReadOnlyList<ToolResultBlock> BuildBlocks(IToolModule module, ToolPageSpec spec, string output, IReadOnlyList<string> lines, string language)
    {
        if (string.IsNullOrWhiteSpace(output))
        {
            return [ToolResultBlock.List(ToolResultBlockKind.Status, T(language, "空结果", "Empty result"), [new("info", T(language, "暂无可展示的结果。", "There is no result to display."), string.Empty, "info", string.Empty)])];
        }

        if (module.Id is "qr_generator" && LooksLikeSvg(output))
        {
            return
            [
                ToolResultBlock.Media(
                    ToolResultBlockKind.Image,
                    T(language, "二维码预览", "QR code preview"),
                    output,
                    string.Empty,
                    new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        ["contentType"] = "image/svg+xml",
                        ["displayMode"] = "preview",
                        ["rawSvg"] = output
                    })
            ];
        }

        if (module.Id is "weather")
        {
            return [ToolResultBlock.Metric(T(language, "天气摘要", "Weather summary"), KeyValues(lines).ToArray())];
        }

        if (module.Id is "train_query")
        {
            return BuildTrainBlocks(lines, language);
        }

        if (module.Id is "dns_query")
        {
            return BuildDnsBlocks(lines, language);
        }

        if (module.Id is "ip_lookup" or "ip_info" or "rdap_ip_lookup" or "rdap_domain_lookup" or "domain_price")
        {
            return BuildRdapBlocks(lines, language);
        }

        if (IsReferenceTool(module.Id))
        {
            return BuildReferenceBlocksV2(lines, language);
        }

        if (module.Id is "smart_search")
        {
            return BuildSearchBlocks(lines, language);
        }

        if (module.Id is "system_tool" or "system_info" or "pc_benchmark")
        {
            return BuildSystemBlocks(lines, language);
        }

        if (spec.Result == ToolResultKind.JsonTree || LooksLikeJson(output))
        {
            return BuildJsonBlocks(output, language);
        }

        if (module.Id is "jwt_decoder")
        {
            return BuildJwtBlocks(output, language);
        }

        if (spec.Result == ToolResultKind.Diff || module.Id is "text_diff")
        {
            return [ToolResultBlock.List(ToolResultBlockKind.Diff, T(language, "差异结果", "Diff result"), lines.Select(line => new ToolResultListItem(DiffLeading(line), line, string.Empty, DiffStatus(line), string.Empty)).ToArray())];
        }

        if (module.Id is "base64_codec" or "url_codec" or "html_entity" or "unicode_codec" or "punycode_codec")
        {
            return BuildCodecBlocks(lines, language);
        }

        if (module.Id is "hash_generator" or "hmac_generator")
        {
            return [ToolResultBlock.Table(T(language, "摘要值", "Digest values"), DigestRows(lines, language))];
        }

        if (spec.Result == ToolResultKind.CodePreview || module.Id.Contains("formatter", StringComparison.OrdinalIgnoreCase) || module.Id.Contains("minifier", StringComparison.OrdinalIgnoreCase))
        {
            return [ToolResultBlock.Code(T(language, "代码预览", "Code preview"), output, CodeLanguage(module.Id))];
        }

        if (spec.Result == ToolResultKind.CalculatorTable || module.Metadata.Category == ToolCategory.Calculator || spec.Layout is ToolLayoutKind.CalculatorForm or ToolLayoutKind.UnitConverter)
        {
            return [ToolResultBlock.Metric(T(language, "计算结果", "Calculation result"), MetricPairs(lines).ToArray())];
        }

        if (spec.Result is ToolResultKind.FileCards or ToolResultKind.ImagePreview or ToolResultKind.Media or ToolResultKind.ColorSwatch)
        {
            return ToolResultDocument.FromOutput(module, output).Blocks;
        }

        if (spec.Result == ToolResultKind.Table)
        {
            var rows = ParseTableRows(lines);
            return rows.Count == 0 ? GenericTextBlocks(lines, language) : [ToolResultBlock.Table(T(language, "表格结果", "Table result"), rows)];
        }

        if (spec.Result is ToolResultKind.RankedList or ToolResultKind.NewsCards)
        {
            return spec.Result == ToolResultKind.NewsCards
                ? BuildNewsCards(lines, T(language, "新闻列表", "News list"))
                : BuildRankedCards(lines, T(language, "榜单", "Ranked list"));
        }

        if (spec.Result == ToolResultKind.StatusList)
        {
            return [ToolResultBlock.List(ToolResultBlockKind.Status, T(language, "状态", "Status"), lines.Select(line => new ToolResultListItem(StatusLeading(line), line, string.Empty, StatusOf(line), string.Empty)).ToArray())];
        }

        var keyValues = KeyValues(lines).ToArray();
        if (keyValues.Length >= 2)
        {
            return [ToolResultBlock.KeyValue(T(language, "详情", "Details"), keyValues)];
        }

        return GenericTextBlocks(lines, language);
    }

    private static IReadOnlyList<ToolResultBlock> BuildRemoteBlocks(string id, IReadOnlyList<string> lines, string language)
    {
        if (id.Equals("weather", StringComparison.OrdinalIgnoreCase))
        {
            return BuildWeatherBlocks(lines, language);
        }

        return id switch
        {
            "dns_query" => BuildDnsBlocks(lines, language),
            "weather" => [ToolResultBlock.Metric(T(language, "天气摘要", "Weather summary"), KeyValues(lines).ToArray())],
            "ip_info" or "ip_lookup" or "rdap_ip_lookup" or "rdap_domain_lookup" or "domain_price" => BuildRdapBlocks(lines, language),
            "baidu_hot" or "hotboard" or "bili_hot" or "zhihu_hot" or "earthquake_info" or "movie_box_office" => BuildRankedCards(lines, T(language, "数据列表", "Data list")),
            "history_today" => BuildHistoryBlocks(lines, language),
            "ai_latest_news" or "tech_news" or "football_news" or "cctv_news" => BuildNewsCards(lines, T(language, "新闻列表", "News list")),
            "gold_price" => BuildGoldPriceBlocks(lines, language),
            "oil_price" or "wx_domain_check" or "http_diagnostic" => BuildHtmlSummaryBlocks(lines, language),
            "city_route_query" => BuildMetricTextBlocks(lines, T(language, "城际路线", "City route"), language),
            "train_query" => BuildTrainBlocks(lines, language),
            _ => []
        };
    }

    private static IReadOnlyList<ToolResultBlock> BuildHistoryBlocks(IReadOnlyList<string> lines, string language)
    {
        var blocks = new List<ToolResultBlock>();
        var currentSection = string.Empty;
        var items = new List<ToolResultListItem>();

        void Flush()
        {
            if (items.Count > 0)
            {
                blocks.Add(ToolResultBlock.Timeline(
                    string.IsNullOrWhiteSpace(currentSection) ? T(language, "历史事件", "History") : currentSection,
                    items.ToArray(),
                    BlockMeta("text/plain", "timeline", "normal")));
                items.Clear();
            }
        }

        foreach (var line in lines)
        {
            var trimmed = line.Trim();
            if (IsRemoteHeaderOrPrivacyLine(trimmed))
            {
                continue;
            }
            if (string.IsNullOrWhiteSpace(trimmed))
            {
                continue;
            }

            if (!trimmed.StartsWith("-", StringComparison.Ordinal) && trimmed == trimmed.ToUpperInvariant() && trimmed.Length <= 24)
            {
                Flush();
                currentSection = trimmed;
                continue;
            }

            var match = Regex.Match(trimmed, @"^-?\s*(?<year>-?\d{1,4})\s*:\s*(?<text>.+)$");
            if (match.Success)
            {
                items.Add(new ToolResultListItem(match.Groups["year"].Value, match.Groups["text"].Value, currentSection, "info", ExtractLinks([line]).FirstOrDefault() ?? string.Empty));
            }
            else
            {
                items.Add(new ToolResultListItem("•", trimmed.TrimStart('-', ' '), currentSection, "info", ExtractLinks([line]).FirstOrDefault() ?? string.Empty));
            }
        }

        Flush();
        return blocks.Count == 0 ? GenericTextBlocks(lines, language) : blocks;
    }

    private static IReadOnlyList<ToolResultBlock> BuildMetricTextBlocks(IReadOnlyList<string> lines, string title, string language)
    {
        var pairs = KeyValues(lines).ToArray();
        if (pairs.Length > 0)
        {
            return [ToolResultBlock.Metric(title, pairs, BlockMeta("metric", "summary", "high"))];
        }

        return BuildHtmlSummaryBlocks(lines, language);
    }

    private static IReadOnlyList<ToolResultBlock> BuildGoldPriceBlocks(IReadOnlyList<string> lines, string language)
    {
        var blocks = new List<ToolResultBlock>();
        var pairs = KeyValues(lines).ToArray();
        if (pairs.Length > 0)
        {
            blocks.Add(ToolResultBlock.Metric(T(language, "黄金价格", "Gold price"), pairs, BlockMeta("metric", "summary", "high")));
        }

        var rows = ParseTableRows(lines);
        if (rows.Count > 1)
        {
            blocks.Add(ToolResultBlock.Table(T(language, "近期定盘价", "Recent fixes"), rows, BlockMeta("table", "details", "normal")));

            var chartRows = rows
                .Skip(1)
                .Where(row => row.Length >= 2 && TryParseDecimal(row[1], out _))
                .Select(row => new[] { row[0], row[1] })
                .Take(60)
                .ToArray();
            if (chartRows.Length >= 2)
            {
                blocks.Add(ToolResultBlock.LineChart(
                    T(language, "USD/oz 趋势", "USD/oz trend"),
                    new[] { new[] { T(language, "日期", "Date"), "USD/oz" } }.Concat(chartRows).ToArray(),
                    new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        ["contentType"] = "chart/line",
                        ["displayMode"] = "chart",
                        ["priority"] = "high",
                        ["chartUnit"] = "USD/oz"
                    }));
            }
        }

        return blocks.Count == 0
            ? BuildMetricTextBlocks(lines, T(language, "黄金价格", "Gold price"), language)
            : blocks;
    }

    private static IReadOnlyList<ToolResultBlock> BuildHtmlSummaryBlocks(IReadOnlyList<string> lines, string language)
    {
        var blocks = new List<ToolResultBlock>();
        var title = lines.FirstOrDefault(line => !string.IsNullOrWhiteSpace(line) && !Regex.IsMatch(line, @"^\d+[\.\)]")) ?? string.Empty;
        if (!string.IsNullOrWhiteSpace(title))
        {
            blocks.Add(ToolResultBlock.Metric(
                T(language, "查询摘要", "Query summary"),
                [Pair(T(language, "标题", "Title"), title)],
                BlockMeta("text/plain", "summary", "high")));
        }

        var cards = BuildRankedCards(lines.Where(line => Regex.IsMatch(line.Trim(), @"^\d+[\.\)]")).ToArray(), T(language, "详情", "Details"));
        blocks.AddRange(cards);

        if (blocks.Count == 0)
        {
            blocks.AddRange(GenericTextBlocks(lines, language));
        }

        return blocks;
    }

    private static IReadOnlyList<ToolResultBlock> BuildJsonBlocks(string output, string language)
    {
        try
        {
            using var json = JsonDocument.Parse(output);
            var root = json.RootElement;
            var blocks = new List<ToolResultBlock>
            {
                ToolResultBlock.JsonTree(T(language, "JSON 树", "JSON tree"), JsonSerializer.Serialize(root, new JsonSerializerOptions { WriteIndented = true }))
            };

            if (root.ValueKind == JsonValueKind.Object)
            {
                blocks.Insert(0, ToolResultBlock.KeyValue(
                    T(language, "字段摘要", "Field summary"),
                    root.EnumerateObject().Take(40).Select(property => Pair(property.Name, JsonPreview(property.Value))).ToArray()));
            }
            else if (root.ValueKind == JsonValueKind.Array)
            {
                blocks.Insert(0, ToolResultBlock.Metric(T(language, "数组摘要", "Array summary"), [Pair(T(language, "项目数", "Items"), root.GetArrayLength().ToString())]));
                var rows = JsonArrayRows(root).ToArray();
                if (rows.Length > 0)
                {
                    blocks.Insert(1, ToolResultBlock.Table(T(language, "数组预览", "Array preview"), rows));
                }
            }

            return blocks;
        }
        catch (JsonException exception)
        {
            return
            [
                ToolResultBlock.List(ToolResultBlockKind.Status, T(language, "JSON 解析失败", "JSON parse failed"), [new("error", exception.Message, string.Empty, "error", string.Empty)]),
                ToolResultBlock.Raw(T(language, "原始内容", "Raw content"), output, "json")
            ];
        }
    }

    private static IReadOnlyList<ToolResultBlock> BuildJwtBlocks(string output, string language)
    {
        var blocks = new List<ToolResultBlock>();
        var sections = output.Replace("\r\n", "\n")
            .Split("\n\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Where(section => section.TrimStart().StartsWith('{') || section.TrimStart().StartsWith('['))
            .Take(3)
            .ToArray();
        foreach (var section in sections)
        {
            blocks.AddRange(BuildJsonBlocks(section, language));
        }

        return blocks.Count == 0 ? [ToolResultBlock.Code("JWT", output, "json")] : blocks;
    }

    private static IReadOnlyList<ToolResultBlock> BuildDnsBlocks(IReadOnlyList<string> lines, string language)
    {
        var records = new List<string[]>
        {
            new[] { T(language, "名称", "Name"), "TYPE", "TTL", T(language, "数据", "Data") }
        };
        var details = new List<KeyValuePair<string, string>>();
        foreach (var line in lines)
        {
            var match = Regex.Match(line, @"^(?<name>.*?)\s+TYPE\s+(?<type>\S+)\s+TTL\s+(?<ttl>\S+)\s+(?<data>.*)$", RegexOptions.IgnoreCase);
            if (match.Success)
            {
                records.Add([match.Groups["name"].Value, match.Groups["type"].Value, match.Groups["ttl"].Value, match.Groups["data"].Value]);
                continue;
            }

            details.AddRange(KeyValues([line]));
        }

        var blocks = new List<ToolResultBlock>();
        if (details.Count > 0)
        {
            blocks.Add(ToolResultBlock.KeyValue(T(language, "查询摘要", "Query summary"), details));
        }
        if (records.Count > 1)
        {
            blocks.Add(ToolResultBlock.Table("DNS", records));
        }
        return blocks;
    }

    private static IReadOnlyList<ToolResultBlock> BuildWeatherBlocks(IReadOnlyList<string> lines, string language)
    {
        var pairs = KeyValues(lines).ToList();
        var blocks = new List<ToolResultBlock>();
        if (pairs.Count > 0)
        {
            blocks.Add(ToolResultBlock.Metric(
                T(language, "天气摘要", "Weather summary"),
                pairs.Take(8).ToArray(),
                BlockMeta("metric", "summary", "high")));
        }

        var forecastRows = new List<string[]> { new[] { T(language, "日期", "Date"), T(language, "最低", "Low"), T(language, "最高", "High"), T(language, "说明", "Note") } };
        foreach (var line in lines)
        {
            var match = Regex.Match(line, @"^(?<date>\d{4}-\d{2}-\d{2})\s*:\s*(?<low>-?\d+(?:\.\d+)?)\s*-\s*(?<high>-?\d+(?:\.\d+)?)(?<unit>.*)$");
            if (match.Success)
            {
                forecastRows.Add([
                    match.Groups["date"].Value,
                    match.Groups["low"].Value,
                    match.Groups["high"].Value,
                    match.Groups["unit"].Value.Trim()
                ]);
            }
        }

        if (forecastRows.Count > 1)
        {
            blocks.Add(ToolResultBlock.Table(
                T(language, "未来预报", "Forecast"),
                forecastRows,
                BlockMeta("table", "details", "normal")));
        }

        return blocks.Count > 0 ? blocks : GenericTextBlocks(lines, language);
    }

    private static IReadOnlyList<ToolResultBlock> BuildRdapBlocks(IReadOnlyList<string> lines, string language)
    {
        var events = new List<ToolResultListItem>();
        var pairs = new List<KeyValuePair<string, string>>();
        foreach (var pair in KeyValues(lines))
        {
            if (pair.Key.Contains("event", StringComparison.OrdinalIgnoreCase) || pair.Key.Contains("Notice", StringComparison.OrdinalIgnoreCase))
            {
                events.Add(new ToolResultListItem("•", pair.Key, pair.Value, "info", string.Empty));
            }
            else
            {
                pairs.Add(pair);
            }
        }

        var blocks = new List<ToolResultBlock>();
        if (pairs.Count > 0)
        {
            blocks.Add(ToolResultBlock.KeyValue("RDAP", pairs));
        }
        if (events.Count > 0)
        {
            blocks.Add(ToolResultBlock.Timeline(T(language, "事件/公告", "Events / notices"), events));
        }
        return blocks;
    }

    private static IReadOnlyList<ToolResultBlock> BuildReferenceBlocks(IReadOnlyList<string> lines, string language)
    {
        var rows = new List<string[]> { new[] { T(language, "代码", "Code"), T(language, "名称", "Name"), T(language, "说明", "Description"), T(language, "备注", "Note") } };
        var sourcePairs = new List<KeyValuePair<string, string>>();
        foreach (var line in lines)
        {
            if (line.StartsWith("数据源：", StringComparison.Ordinal) || line.StartsWith("Data source:", StringComparison.OrdinalIgnoreCase))
            {
                sourcePairs.Add(Pair(T(language, "数据源", "Data source"), SplitValue(line)));
                continue;
            }

            var match = Regex.Match(line, @"^(?<code>.*?)\s+-\s+(?<name>.*?)(?:[:：]\s*(?<desc>.*?))?(?:\s+/\s+(?<note>.*))?$");
            if (match.Success)
            {
                rows.Add([match.Groups["code"].Value, match.Groups["name"].Value, match.Groups["desc"].Value, match.Groups["note"].Value]);
            }
        }

        var blocks = new List<ToolResultBlock>();
        if (sourcePairs.Count > 0)
        {
            blocks.Add(ToolResultBlock.KeyValue(T(language, "来源", "Source"), sourcePairs));
        }
        if (rows.Count > 1)
        {
            blocks.Add(ToolResultBlock.Table(T(language, "参考数据", "Reference data"), rows));
        }
        return blocks.Count == 0 ? GenericTextBlocks(lines, language) : blocks;
    }

    private static IReadOnlyList<ToolResultBlock> BuildSearchBlocks(IReadOnlyList<string> lines, string language)
    {
        var pairs = KeyValues(lines).ToList();
        var links = ExtractLinks(lines).Distinct(StringComparer.OrdinalIgnoreCase).Select(link => ToolResultBlock.Link(T(language, "打开入口", "Open entry"), link, link)).ToList();
        var blocks = new List<ToolResultBlock>();
        if (pairs.Count > 0)
        {
            blocks.Add(ToolResultBlock.KeyValue(T(language, "搜索信息", "Search info"), pairs));
        }
        blocks.AddRange(links);
        return blocks.Count == 0 ? GenericTextBlocks(lines, language) : blocks;
    }

    private static IReadOnlyList<ToolResultBlock> BuildReferenceBlocksV2(IReadOnlyList<string> lines, string language)
    {
        var rows = new List<string[]> { new[] { T(language, "代码", "Code"), T(language, "名称", "Name"), T(language, "说明", "Description"), T(language, "备注", "Note") } };
        var sourcePairs = new List<KeyValuePair<string, string>>();
        foreach (var line in lines)
        {
            if (IsSourceLine(line))
            {
                sourcePairs.Add(Pair(T(language, "数据源", "Data source"), SplitValue(line)));
                continue;
            }

            var match = Regex.Match(line, @"^(?<code>.*?)\s+-\s+(?<name>.*?)(?:[:：]\s*(?<desc>.*?))?(?:\s+/\s+(?<note>.*))?$");
            if (match.Success)
            {
                rows.Add([match.Groups["code"].Value, match.Groups["name"].Value, match.Groups["desc"].Value, match.Groups["note"].Value]);
            }
        }

        var blocks = new List<ToolResultBlock>();
        if (rows.Count > 1)
        {
            blocks.Add(ToolResultBlock.Metric(
                T(language, "参考摘要", "Reference summary"),
                [
                    Pair(T(language, "命中数", "Matches"), (rows.Count - 1).ToString()),
                    Pair(T(language, "展示上限", "Display limit"), "80")
                ],
                BlockMeta("metric", "summary", "high")));
            blocks.Add(ToolResultBlock.Table(T(language, "参考数据", "Reference data"), rows, BlockMeta("table", "details", "normal")));
        }
        if (sourcePairs.Count > 0)
        {
            blocks.Add(ToolResultBlock.KeyValue(T(language, "来源", "Source"), sourcePairs, BlockMeta("text/plain", "source", "normal")));
        }

        return blocks.Count == 0 ? GenericTextBlocks(lines, language) : blocks;
    }

    private static IReadOnlyList<ToolResultBlock> BuildCodecBlocks(IReadOnlyList<string> lines, string language)
    {
        var blocks = new List<ToolResultBlock>();
        var sections = new List<KeyValuePair<string, string>>();
        var currentTitle = string.Empty;
        var currentText = new List<string>();

        void Flush()
        {
            if (!string.IsNullOrWhiteSpace(currentTitle))
            {
                sections.Add(Pair(currentTitle.TrimEnd(':'), string.Join(Environment.NewLine, currentText).Trim()));
            }
            currentText.Clear();
        }

        foreach (var line in lines)
        {
            if (line.EndsWith(":", StringComparison.Ordinal) && line.Length < 32)
            {
                Flush();
                currentTitle = line;
            }
            else
            {
                currentText.Add(line);
            }
        }
        Flush();

        if (sections.Count > 0)
        {
            blocks.Add(ToolResultBlock.KeyValue(T(language, "转换结果", "Conversion result"), sections));
        }
        else
        {
            blocks.Add(ToolResultBlock.Code(T(language, "转换文本", "Converted text"), string.Join(Environment.NewLine, lines)));
        }
        return blocks;
    }

    private static IReadOnlyList<string[]> DigestRows(IReadOnlyList<string> lines, string language)
    {
        var rows = new List<string[]> { new[] { T(language, "算法", "Algorithm"), T(language, "摘要", "Digest") } };
        foreach (var pair in KeyValues(lines))
        {
            rows.Add([pair.Key, pair.Value]);
        }
        if (rows.Count == 1)
        {
            rows.AddRange(lines.Select((line, index) => new[] { (index + 1).ToString(), line }));
        }
        return rows;
    }

    private static IReadOnlyList<ToolResultBlock> BuildSystemBlocks(IReadOnlyList<string> lines, string language)
    {
        var metrics = KeyValues(lines).ToArray();
        return metrics.Length > 0
            ? [ToolResultBlock.Metric(T(language, "系统摘要", "System summary"), metrics)]
            : GenericTextBlocks(lines, language);
    }

    private static IReadOnlyList<ToolResultBlock> BuildTrainBlocks(IReadOnlyList<string> lines, string language)
    {
        var rows = new List<string[]> { new[] { T(language, "车次/车站", "Train / station"), T(language, "信息", "Info"), T(language, "备注", "Note") } };
        foreach (var line in lines.Where(line => !line.Contains("数据源", StringComparison.Ordinal) && !line.StartsWith("Fetched", StringComparison.OrdinalIgnoreCase)))
        {
            var parts = line.Split("  ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
            if (parts.Length >= 2)
            {
                rows.Add([parts[0], string.Join(" / ", parts.Skip(1)), string.Empty]);
            }
            else if (line.Contains(" / ", StringComparison.Ordinal))
            {
                var station = line.Split(" / ", StringSplitOptions.TrimEntries);
                rows.Add([station.ElementAtOrDefault(0) ?? string.Empty, station.ElementAtOrDefault(1) ?? string.Empty, station.ElementAtOrDefault(2) ?? string.Empty]);
            }
        }
        return rows.Count > 1 ? [ToolResultBlock.Table(T(language, "列车/车站", "Train / station"), rows)] : GenericTextBlocks(lines, language);
    }

    private static IReadOnlyList<ToolResultBlock> BuildRankedCards(IReadOnlyList<string> lines, string title)
    {
        var items = new List<ToolResultListItem>();
        foreach (var line in lines)
        {
            var trimmed = line.Trim();
            if (IsRemoteHeaderOrPrivacyLine(trimmed))
            {
                continue;
            }
            var match = Regex.Match(trimmed, @"^(?<rank>\d+)[\.\)、]\s*(?<title>.*)$");
            if (match.Success)
            {
                var visibleTitle = CleanVisibleText(match.Groups["title"].Value);
                var uri = ExtractLinks([line]).FirstOrDefault() ?? string.Empty;
                items.Add(new ToolResultListItem(match.Groups["rank"].Value, visibleTitle, string.Empty, "info", uri));
            }
            else if (items.Count > 0 && trimmed.Length > 0 && !trimmed.Contains("数据源", StringComparison.Ordinal))
            {
                var previous = items[^1];
                var visible = CleanVisibleText(trimmed);
                items[^1] = previous with { Subtitle = string.IsNullOrWhiteSpace(previous.Subtitle) ? visible : $"{previous.Subtitle}\n{visible}" };
            }
        }
        return items.Count == 0 ? [] : [ToolResultBlock.List(ToolResultBlockKind.RankedList, title, items)];
    }

    private static IReadOnlyList<ToolResultBlock> BuildNewsCards(IReadOnlyList<string> lines, string title)
    {
        var items = new List<ToolResultListItem>();
        foreach (var line in lines)
        {
            var trimmed = line.Trim();
            if (IsRemoteHeaderOrPrivacyLine(trimmed) || string.IsNullOrWhiteSpace(trimmed))
            {
                continue;
            }

            var match = Regex.Match(trimmed, @"^(?<rank>\d+)[\.\)\u3001\uff09\s]+(?<title>.*)$");
            if (match.Success)
            {
                var visibleTitle = CleanVisibleText(match.Groups["title"].Value);
                var uri = ExtractLinks([line]).FirstOrDefault() ?? string.Empty;
                items.Add(new ToolResultListItem(match.Groups["rank"].Value, visibleTitle, string.Empty, "info", uri));
                continue;
            }

            if (items.Count > 0)
            {
                var previous = items[^1];
                var visible = CleanVisibleText(trimmed);
                items[^1] = previous with { Subtitle = string.IsNullOrWhiteSpace(previous.Subtitle) ? visible : $"{previous.Subtitle}\n{visible}" };
            }
        }

        return items.Count == 0 ? [] : [ToolResultBlock.List(ToolResultBlockKind.NewsList, title, items)];
    }

    private static IReadOnlyList<ToolResultBlock> GenericTextBlocks(IReadOnlyList<string> lines, string language)
    {
        return [ToolResultBlock.List(ToolResultBlockKind.Text, T(language, "结果", "Result"), lines.Take(160).Select((line, index) => new ToolResultListItem((index + 1).ToString(), CleanVisibleText(line), string.Empty, "info", ExtractLinks([line]).FirstOrDefault() ?? string.Empty)).ToArray())];
    }

    private static IReadOnlyList<KeyValuePair<string, string>> KeyValues(IReadOnlyList<string> lines)
    {
        var pairs = new List<KeyValuePair<string, string>>();
        foreach (var line in lines)
        {
            var normalized = line.Trim();
            var separators = new[] { "：", ":", "=", "\t" };
            foreach (var separator in separators)
            {
                var index = normalized.IndexOf(separator, StringComparison.Ordinal);
                if (index > 0 && index < normalized.Length - separator.Length)
                {
                    var key = normalized[..index].Trim().Trim('-', '*', ' ');
                    var value = normalized[(index + separator.Length)..].Trim();
                    if (key.Length is > 0 and < 64 && value.Length > 0)
                    {
                        pairs.Add(Pair(key, value));
                    }
                    break;
                }
            }
        }
        return pairs;
    }

    private static IEnumerable<KeyValuePair<string, string>> MetricPairs(IReadOnlyList<string> lines)
    {
        var pairs = KeyValues(lines).ToArray();
        if (pairs.Length > 0)
        {
            return pairs;
        }
        return lines.Take(12).Select((line, index) => Pair((index + 1).ToString(), line));
    }

    private static IReadOnlyList<string[]> ParseTableRows(IReadOnlyList<string> lines)
    {
        return lines
            .Where(line => line.Contains('|') || line.Contains('\t'))
            .Select(line => line.Contains('|') ? line.Trim().Trim('|').Split('|', StringSplitOptions.TrimEntries) : line.Split('\t', StringSplitOptions.TrimEntries))
            .Where(row => row.Length >= 2)
            .Take(80)
            .ToArray();
    }

    private static bool TryParseDecimal(string value, out decimal number)
    {
        var cleaned = Regex.Replace(value ?? string.Empty, @"[^\d\.\,\-]", string.Empty).Replace(",", string.Empty, StringComparison.Ordinal);
        return decimal.TryParse(cleaned, NumberStyles.Float, CultureInfo.InvariantCulture, out number) ||
            decimal.TryParse(cleaned, NumberStyles.Float, CultureInfo.CurrentCulture, out number);
    }

    private static IEnumerable<string[]> JsonArrayRows(JsonElement root)
    {
        if (root.ValueKind != JsonValueKind.Array)
        {
            yield break;
        }
        var items = root.EnumerateArray().Take(20).ToArray();
        if (items.Length == 0)
        {
            yield break;
        }
        if (items.Any(item => item.ValueKind != JsonValueKind.Object))
        {
            yield return ["#", "Value"];
            for (var index = 0; index < items.Length; index++)
            {
                yield return [(index + 1).ToString(), JsonPreview(items[index])];
            }
            yield break;
        }
        var headers = items.SelectMany(item => item.EnumerateObject().Select(property => property.Name)).Distinct(StringComparer.Ordinal).Take(8).ToArray();
        yield return headers;
        foreach (var item in items)
        {
            yield return headers.Select(header => item.TryGetProperty(header, out var value) ? JsonPreview(value) : string.Empty).ToArray();
        }
    }

    private static string JsonPreview(JsonElement value)
    {
        return value.ValueKind switch
        {
            JsonValueKind.String => value.GetString() ?? string.Empty,
            JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False => value.GetRawText(),
            JsonValueKind.Null => "null",
            JsonValueKind.Array => $"Array[{value.GetArrayLength()}]",
            JsonValueKind.Object => JsonSerializer.Serialize(value),
            _ => value.GetRawText()
        };
    }

    private static bool IsReferenceTool(string id)
    {
        return id is "mime_lookup" or "http_status_lookup" or "port_lookup" or "dns_record_lookup" or "unicode_block_lookup" or "charset_lookup" or "timezone_abbr_lookup" or "regex_preset_lookup" or "magic_number_lookup" or "car_info" or "sanguosha_skin";
    }

    private static ToolResultKind GuessRemoteResultKind(string id)
    {
        return id is "baidu_hot" or "hotboard" or "bili_hot" or "zhihu_hot" or "movie_box_office" or "history_today"
            ? ToolResultKind.RankedList
            : id is "tech_news" or "football_news" or "cctv_news" or "ai_latest_news"
                ? ToolResultKind.NewsCards
                : id is "city_route_query"
                    ? ToolResultKind.KeyValueCards
                : ToolResultKind.KeyValueCards;
    }

    private static ToolResultDocument Document(string toolId, ToolResultKind kind, string output, IReadOnlyList<ToolResultBlock> blocks, string language)
    {
        return new ToolResultDocument(toolId, kind, output, blocks, Metadata(toolId, kind, language, DisplayProfile(kind)), output, string.Empty, "ok");
    }

    private static Dictionary<string, string> Metadata(string toolId, ToolResultKind kind, string language, string displayProfile)
    {
        return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["toolId"] = toolId,
            ["resultKind"] = kind.ToString(),
            ["language"] = language,
            ["generatedAt"] = DateTimeOffset.Now.ToString("O"),
            ["loadedAt"] = DateTimeOffset.Now.ToString("O"),
            ["dataFreshness"] = "live",
            ["displayProfile"] = displayProfile
        };
    }

    private static Dictionary<string, string> RemoteMetadata(ApiEndpoint endpoint, ToolResultKind kind, string language)
    {
        var metadata = Metadata(endpoint.Id, kind, language, "remote-dashboard");
        metadata["sourceName"] = endpoint.SourceName;
        metadata["sourceVisibility"] = endpoint.SourceVisibility.ToString();
        metadata["sourceSensitivity"] = endpoint.SourceVisibility == ApiSourceVisibility.SensitivePrivate ? "sensitive" : "public";
        metadata["dataFreshness"] = "remote-live";

        return metadata;
    }

    private static string DisplayProfile(ToolResultKind kind)
    {
        return kind switch
        {
            ToolResultKind.RankedList or ToolResultKind.NewsCards => "cards",
            ToolResultKind.Table or ToolResultKind.CalculatorTable or ToolResultKind.ReferenceRows => "table",
            ToolResultKind.ImagePreview or ToolResultKind.Media => "preview",
            ToolResultKind.JsonTree or ToolResultKind.CodePreview => "document",
            _ => "summary-details"
        };
    }

    private static IReadOnlyDictionary<string, string> BlockMeta(string contentType, string displayMode, string priority)
    {
        return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["contentType"] = contentType,
            ["displayMode"] = displayMode,
            ["priority"] = priority
        };
    }

    private static KeyValuePair<string, string> Pair(string key, string value) => new(key, value);

    private static IReadOnlyList<string> Lines(string output)
    {
        return output.Replace("\r\n", "\n").Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
    }

    private static IReadOnlyList<string> RemoveSourceHeader(IReadOnlyList<string> lines)
    {
        return lines
            .Where(line => !IsRemoteHeaderOrPrivacyLine(line))
            .ToArray();
    }

    private static bool IsRemoteHeaderOrPrivacyLine(string line)
    {
        var normalized = line.Trim();
        return IsSourceLine(normalized) ||
            normalized.StartsWith("\u6570\u636e\u6e90", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("\u6765\u6e90", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("\u83b7\u53d6\u65f6\u95f4", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("Source note:", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("remote address is hidden", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("sanitized source name", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("\u6765\u6e90\u8bf4\u660e", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("\u5df2\u9690\u85cf\u8fdc\u7a0b\u5730\u5740", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("\u8131\u654f\u6765\u6e90", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("璇存槑", StringComparison.OrdinalIgnoreCase) && normalized.Contains("闅愯棌", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("璇存槑", StringComparison.OrdinalIgnoreCase) && normalized.Contains("宸查殣", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("鑴辨晱", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("杩滅▼鍦板潃", StringComparison.OrdinalIgnoreCase) ||
            normalized.Contains("繙绋嬪湴鍧", StringComparison.OrdinalIgnoreCase);
    }

    private static bool IsSourceLine(string line)
    {
        var normalized = line.Trim();
        return normalized.StartsWith("Data source:", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("Fetched at:", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("Source:", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("来源", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("数据源", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("获取时间", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("鏉ユ簮", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("鏁版嵁", StringComparison.OrdinalIgnoreCase) ||
            normalized.StartsWith("鑾峰彇", StringComparison.OrdinalIgnoreCase);
    }

    private static string SplitValue(string line)
    {
        var index = line.IndexOfAny(['：', ':']);
        return index >= 0 && index < line.Length - 1 ? line[(index + 1)..].Trim() : line;
    }

    private static IEnumerable<string> ExtractLinks(IEnumerable<string> lines)
    {
        foreach (var line in lines)
        {
            foreach (Match match in Regex.Matches(line, @"https?://[^\s\]\)>'""]+", RegexOptions.IgnoreCase))
            {
                yield return match.Value.TrimEnd('.', ',', ';');
            }
        }
    }

    private static string CleanVisibleText(string value)
    {
        var text = value ?? string.Empty;
        text = Regex.Replace(text, @"\s*https?://[^\s\]\)>'""]+", string.Empty, RegexOptions.IgnoreCase);
        text = Regex.Replace(text, @"\s*/\s*$", string.Empty);
        text = Regex.Replace(text, @"\s{2,}", " ");
        return text.Trim();
    }

    private static bool LooksLikeJson(string output)
    {
        var trimmed = output.TrimStart();
        return trimmed.StartsWith('{') || trimmed.StartsWith('[');
    }

    private static bool LooksLikeSvg(string output)
    {
        var trimmed = output.TrimStart();
        return trimmed.StartsWith("<svg", StringComparison.OrdinalIgnoreCase) ||
            (trimmed.StartsWith("<?xml", StringComparison.OrdinalIgnoreCase) &&
             trimmed.Contains("<svg", StringComparison.OrdinalIgnoreCase));
    }

    private static string SourceTypeLabel(ApiEndpoint endpoint, string language)
    {
        return endpoint.SourceVisibility switch
        {
            ApiSourceVisibility.PublicOfficial => T(language, "官方/权威源", "Official / authoritative"),
            ApiSourceVisibility.PublicTrusted => T(language, "公开可信源", "Public trusted"),
            ApiSourceVisibility.SensitivePrivate => T(language, "私有/敏感源", "Private / sensitive"),
            _ => endpoint.IsOfficial ? T(language, "官方/权威源", "Official / authoritative") : T(language, "公开可信源", "Public trusted")
        };
    }

    private static IReadOnlyDictionary<string, string> SourceMetadata(ApiEndpoint endpoint)
    {
        var metadata = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["contentType"] = "text/plain",
            ["displayMode"] = "source",
            ["priority"] = "low",
            ["sourceVisibility"] = endpoint.SourceVisibility.ToString(),
            ["sourceSensitivity"] = endpoint.SourceVisibility == ApiSourceVisibility.SensitivePrivate ? "sensitive" : "public",
            ["sourceName"] = endpoint.SourceName
        };

        return metadata;
    }

    private static string CodeLanguage(string id)
    {
        return id switch
        {
            var value when value.Contains("json", StringComparison.OrdinalIgnoreCase) => "json",
            var value when value.Contains("xml", StringComparison.OrdinalIgnoreCase) => "xml",
            var value when value.Contains("sql", StringComparison.OrdinalIgnoreCase) => "sql",
            var value when value.Contains("html", StringComparison.OrdinalIgnoreCase) => "html",
            _ => string.Empty
        };
    }

    private static string DiffLeading(string line) => line.StartsWith('+') ? "+" : line.StartsWith('-') ? "-" : " ";

    private static string DiffStatus(string line) => line.StartsWith('+') ? "added" : line.StartsWith('-') ? "removed" : "context";

    private static string StatusLeading(string line) => StatusOf(line) switch { "ok" => "OK", "error" => "!", _ => "i" };

    private static string StatusOf(string line)
    {
        return line.Contains("OK", StringComparison.OrdinalIgnoreCase) || line.Contains("SUCCESS", StringComparison.OrdinalIgnoreCase)
            ? "ok"
            : line.Contains("FAIL", StringComparison.OrdinalIgnoreCase) || line.Contains("MISMATCH", StringComparison.OrdinalIgnoreCase) || line.Contains("ERROR", StringComparison.OrdinalIgnoreCase)
                ? "error"
                : "info";
    }

    private static string T(string language, string zh, string en) => string.Equals(language, "en-US", StringComparison.OrdinalIgnoreCase) ? en : zh;
}
