using System.Globalization;
using System.Drawing;
using System.Drawing.Imaging;
using System.Formats.Tar;
using System.IO.Compression;
using System.Net;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Microsoft.VisualBasic;
using QRCoder;
using YMhut.Box.Core;
using YMhut.Box.Core.Api;
using YMhut.Box.Core.Data;
using YMhut.Box.Core.Media;
using ZXing;
using ZXing.Common;
using RuntimeInformation = global::System.Runtime.InteropServices.RuntimeInformation;

namespace YMhut.Box.Core.Tools;

public sealed record ToolExecutionResult(bool Ok, string Output, string? Error = null, ToolResultDocument? Document = null)
{
    public static ToolExecutionResult Success(string output) => new(true, output, null);

    public static ToolExecutionResult Success(string output, ToolResultDocument document) => new(true, output, null, document);

    public static ToolExecutionResult Fail(string error) => new(false, string.Empty, error);
}

public static class ToolExecutor
{
    private static readonly IReadOnlyDictionary<char, char> SimplifiedToTraditional = BuildChineseMap();
    private static readonly IReadOnlyDictionary<char, char> TraditionalToSimplified = SimplifiedToTraditional.ToDictionary(pair => pair.Value, pair => pair.Key);

    private static bool English(string? language) => string.Equals(language, "en-US", StringComparison.OrdinalIgnoreCase);

    private static string T(string? language, string zh, string en) => English(language) ? en : zh;

    public static async Task<ToolExecutionResult> ExecuteAsync(
        IToolModule module,
        string input,
        CancellationToken cancellationToken = default,
        IApiManager? apiManager = null,
        IReferenceDataService? referenceDataService = null,
        string language = "zh-CN")
    {
        try
        {
            var result = module.Id switch
            {
                "json_formatter" => Json(input),
                "base64_codec" => Base64(input),
                "compression_codec" => CompressionCodec(input),
                "url_codec" or "query_builder" => Url(input),
                "html_entity" => ToolExecutionResult.Success(WebUtility.HtmlEncode(input) + Environment.NewLine + Environment.NewLine + WebUtility.HtmlDecode(input)),
                "html_minifier" or "js_obfuscator" => Minify(input),
                "xml_formatter" => Xml(input),
                "hash_generator" => Hashes(input),
                "hmac_generator" => Hmac(input),
                "totp_generator" => Totp(input),
                "password_generator" => Password(input),
                "uuid_generator" => GenerateIds(input, ulid: false),
                "ulid_generator" => GenerateIds(input, ulid: true),
                "random_generator" or "random_picker" => RandomText(input),
                "timestamp_converter" or "date_time_calculator" => Timestamp(input),
                "number_base" or "number_base_converter" => NumberBase(input),
                "unit_converter" or "length_converter" or "weight_converter" or "temperature_converter" or "speed_converter" or "area_converter" or "volume_converter" or "data_size_converter" or "time_duration_converter" => Unit(module.Id, input),
                "calculator_tool" or "percentage_calculator" or "percentage_change_calculator" or "discount_calculator" or "tax_calculator" or "ratio_simplifier" or "average_calculator" or "median_calculator" or "gcd_lcm_calculator" or "permutation_calculator" or "combination_calculator" or "simple_interest_calculator" or "loan_emi_calculator" or "compound_interest_calculator" => Calculator(module.Id, input),
                "bmi_calculator" => Bmi(input),
                "regex_tool" => RegexMatch(input),
                "case_converter" or "slug_generator" => Case(input),
                "chinese_converter" => Chinese(input),
                "text_diff" => TextDiff(input),
                "text_deduplicator" or "duplicate_detector" => Deduplicate(input),
                "text_sorter" => SortLines(input),
                "text_filter" => FilterLines(input),
                "text_statistics" or "line_length_analyzer" or "keyword_counter" or "list_group_statistics" => TextStatistics(input),
                "delimiter_converter" or "line_column_transformer" or "csv_tsv_converter" => Delimiters(input),
                "column_extractor" => Column(input),
                "csv_json_converter" => CsvJson(input),
                "yaml_json_converter" => KeyValueToJson(input),
                "markdown_table_normalizer" => MarkdownTable(input),
                "markdown_preview" => MarkdownPreview(input),
                "json_path_helper" => JsonPaths(input),
                "key_value_converter" or "form_urlencoded_builder" => KeyValueToJson(input),
                "sql_formatter" => Sql(input),
                "hash_manifest_builder" => HashManifest(input),
                "hash_manifest_verify" => VerifyHashManifest(input),
                "batch_text_replace" => ReplaceText(input),
                "filename_formatter" => FileNames(input),
                "color_picker" => ColorTool(input),
                "color_contrast_checker" => ColorContrast(input),
                "qr_generator" => QrGenerate(input),
                "qrcode_scanner" => QrScan(input),
                "archive_tool" => Archive(input),
                "image_processor" => ImageTool(input),
                "image_metadata_inspector" => ImageMetadata(input),
                "ascii_art" => AsciiArt(input),
                "profanity_check" => Profanity(input),
                "unicode_codec" => Unicode(input),
                "punycode_codec" => Punycode(input),
                "jwt_decoder" => Jwt(input),
                "text_splitter" => ToolExecutionResult.Success(string.Join(Environment.NewLine, SplitTokens(input))),
                "text_merger" => ToolExecutionResult.Success(string.Join(" ", Lines(input))),
                "text_numbering" => ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input).Select((line, index) => $"{index + 1}. {line}"))),
                "blank_line_cleaner" => ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input))),
                "whitespace_normalizer" => ToolExecutionResult.Success(Regex.Replace(input.Trim(), @"\s+", " ")),
                "prefix_suffix_batch" => ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input).Select(line => $"prefix_{line}_suffix"))),
                "list_sampler" => ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input).OrderBy(_ => Random.Shared.Next()).Take(5))),
                "list_chunker" => Chunk(input),
                "list_intersection" => SetOperation(input, "intersection"),
                "list_union" => SetOperation(input, "union"),
                "list_difference" => SetOperation(input, "difference"),
                "template_filler" => Template(input),
                "path_normalizer" => PathNormalize(input),
                "path_breakdown" => PathBreakdown(input),
                "extension_extractor" => ExtensionExtract(input),
                "extension_statistics" => ExtensionStats(input),
                "file_manifest_builder" or "manifest_template_export" => Manifest(input),
                "manifest_deduplicator" => Deduplicate(input),
                "manifest_sorter" => SortLines(input),
                "batch_rename_preview" => RenamePreview(input, indexed: false),
                "indexed_rename_preview" => RenamePreview(input, indexed: true),
                "text_trimmer" => ToolExecutionResult.Success(string.Join(Environment.NewLine, input.Replace("\r\n", "\n").Split('\n').Select(line => line.Trim()))),
                "text_reverser" => ToolExecutionResult.Success(new string(input.Reverse().ToArray())),
                "markdown_list_cleaner" => MarkdownList(input),
                "html_text_extractor" => ToolExecutionResult.Success(Regex.Replace(input, "<.*?>", string.Empty, RegexOptions.Singleline).Trim()),
                "url_inspector" => UrlInspect(input),
                "url_redirect_trace" => await UrlRedirectTraceAsync(input, cancellationToken, language).ConfigureAwait(false),
                "domain_extractor" => DomainExtract(input),
                "cidr_subnet_calculator" => Cidr(input),
                "line_pair_zipper" => PairZip(input),
                "date_difference_calculator" or "workday_calculator" or "age_calculator" => DateTool(module.Id, input),
                "cron_helper" => Cron(input),
                "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" => await ReferenceLookupAsync(referenceDataService, module.Id, input, cancellationToken, language).ConfigureAwait(false),
                "car_info" => await CarInfoAsync(referenceDataService, input, cancellationToken).ConfigureAwait(false),
                "sanguosha_skin" => await SanguoshaSkinAsync(referenceDataService, input, cancellationToken).ConfigureAwait(false),
                "qq_avatar" => await QqProfileAsync(input, cancellationToken, apiManager, language).ConfigureAwait(false),
                "shelf_life" => ShelfLife(input),
                "random_cinema" => await RandomCinemaAsync(input, cancellationToken, apiManager, language).ConfigureAwait(false),
                "smart_search" => SmartSearch(input, language),
                "ymhut_uutool_suite" => UuToolSuite(input),
                "system_info" => SystemInfo(),
                "pc_benchmark" => Benchmark(),
                "system_tool" => SystemTool(input, language),
                "safe_browser" or "media_player" or "html_js_playground" or "serial_terminal" => NativeTool(module),
                "ai_translation" => AiTranslation(input),
                "ai_latest_news" => await AiLatestNewsAsync(input, cancellationToken, apiManager, language).ConfigureAwait(false),
                "city_route_query" => await CityRouteQueryAsync(input, cancellationToken, apiManager, language).ConfigureAwait(false),
                "weather" => await WeatherAsync(input, cancellationToken, apiManager, language).ConfigureAwait(false),
                "train_query" => await TrainQueryAsync(input, cancellationToken, apiManager, language).ConfigureAwait(false),
                _ => await ExecuteKnownRemoteOrDescribeAsync(module, input, cancellationToken, apiManager, language).ConfigureAwait(false)
            };
            return result.Ok && result.Document is null
                ? result with { Document = ToolResultBuilder.FromOutput(module, result.Output, language) }
                : result;
        }
        catch (Exception exception)
        {
            return ToolExecutionResult.Fail(SensitiveText.Sanitize(exception.Message));
        }
    }

    private static ToolExecutionResult Json(string input)
    {
        using var document = JsonDocument.Parse(input);
        return ToolExecutionResult.Success(JsonSerializer.Serialize(document.RootElement, new JsonSerializerOptions { WriteIndented = true }));
    }

    private static ToolExecutionResult Base64(string input)
    {
        var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(input));
        var output = $"Encode:{Environment.NewLine}{encoded}";
        try
        {
            var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(input.Trim()));
            output += $"{Environment.NewLine}{Environment.NewLine}Decode:{Environment.NewLine}{decoded}";
        }
        catch (global::System.FormatException)
        {
        }

        return ToolExecutionResult.Success(output);
    }

    private static ToolExecutionResult CompressionCodec(string input)
    {
        var normalized = input.Replace("\r\n", "\n");
        var split = normalized.Split('\n', 2);
        var command = split[0].Trim().ToLowerInvariant();
        var hasCommand = command.Contains("encode") || command.Contains("decode") ||
            command.Contains("compress") || command.Contains("decompress") ||
            command.Contains("gzip") || command.Contains("deflate") || command.Contains("brotli");
        var payload = hasCommand && split.Length > 1 ? split[1] : input;
        var algorithm = command.Contains("brotli") ? "brotli" : command.Contains("deflate") ? "deflate" : "gzip";
        var decode = command.Contains("decode") || command.Contains("decompress");

        if (decode)
        {
            byte[] compressed;
            try
            {
                compressed = Convert.FromBase64String(payload.Trim());
            }
            catch (global::System.FormatException)
            {
                return ToolExecutionResult.Fail("请输入 Base64 压缩数据。");
            }

            using var inputStream = new MemoryStream(compressed);
            using var codec = OpenDecompressionStream(inputStream, algorithm);
            using var output = new MemoryStream();
            codec.CopyTo(output);
            return ToolExecutionResult.Success(Encoding.UTF8.GetString(output.ToArray()));
        }

        var bytes = Encoding.UTF8.GetBytes(payload);
        using var target = new MemoryStream();
        using (var codec = OpenCompressionStream(target, algorithm))
        {
            codec.Write(bytes, 0, bytes.Length);
        }

        var compressedBytes = target.ToArray();
        var ratio = bytes.Length == 0 ? 0 : (double)compressedBytes.Length / bytes.Length;
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"Algorithm: {algorithm}",
            $"Original bytes: {bytes.Length}",
            $"Compressed bytes: {compressedBytes.Length}",
            $"Ratio: {ratio:0.###}",
            $"Base64: {Convert.ToBase64String(compressedBytes)}"
        }));
    }

    private static Stream OpenCompressionStream(Stream target, string algorithm)
    {
        return algorithm switch
        {
            "brotli" => new BrotliStream(target, CompressionLevel.Optimal, leaveOpen: true),
            "deflate" => new DeflateStream(target, CompressionLevel.Optimal, leaveOpen: true),
            _ => new GZipStream(target, CompressionLevel.Optimal, leaveOpen: true)
        };
    }

    private static Stream OpenDecompressionStream(Stream source, string algorithm)
    {
        return algorithm switch
        {
            "brotli" => new BrotliStream(source, CompressionMode.Decompress, leaveOpen: true),
            "deflate" => new DeflateStream(source, CompressionMode.Decompress, leaveOpen: true),
            _ => new GZipStream(source, CompressionMode.Decompress, leaveOpen: true)
        };
    }

    private static ToolExecutionResult Url(string input)
    {
        return ToolExecutionResult.Success(
            $"Encode:{Environment.NewLine}{Uri.EscapeDataString(input)}{Environment.NewLine}{Environment.NewLine}Decode:{Environment.NewLine}{Uri.UnescapeDataString(input)}");
    }

    private static ToolExecutionResult Minify(string input)
    {
        var withoutBlockComments = Regex.Replace(input, @"/\*.*?\*/", string.Empty, RegexOptions.Singleline);
        var withoutLineComments = Regex.Replace(withoutBlockComments, @"^\s*//.*$", string.Empty, RegexOptions.Multiline);
        return ToolExecutionResult.Success(Regex.Replace(withoutLineComments, @">\s+<", "><").Trim());
    }

    private static ToolExecutionResult Xml(string input)
    {
        var document = XDocument.Parse(input);
        return ToolExecutionResult.Success(document.ToString());
    }

    private static ToolExecutionResult Hashes(string input)
    {
        var bytes = Encoding.UTF8.GetBytes(input);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"MD5:    {Hex(MD5.HashData(bytes))}",
            $"SHA1:   {Hex(SHA1.HashData(bytes))}",
            $"SHA256: {Hex(SHA256.HashData(bytes))}",
            $"SHA512: {Hex(SHA512.HashData(bytes))}"
        }));
    }

    private static ToolExecutionResult Hmac(string input)
    {
        var lines = input.Replace("\r\n", "\n").Split('\n', 2);
        var key = lines.Length > 1 ? lines[0] : "ymhut";
        var payload = lines.Length > 1 ? lines[1] : input;
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
        return ToolExecutionResult.Success(Hex(hmac.ComputeHash(Encoding.UTF8.GetBytes(payload))));
    }

    private static ToolExecutionResult Totp(string input)
    {
        var lines = Lines(input).ToArray();
        var secret = lines.FirstOrDefault(line => !line.Contains('=')) ?? "JBSWY3DPEHPK3PXP";
        var options = lines
            .Select(line => line.Split('=', 2))
            .Where(parts => parts.Length == 2)
            .ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim(), StringComparer.OrdinalIgnoreCase);
        var period = options.TryGetValue("period", out var periodText) && int.TryParse(periodText, out var parsedPeriod)
            ? Math.Clamp(parsedPeriod, 15, 120)
            : 30;
        var digits = options.TryGetValue("digits", out var digitsText) && int.TryParse(digitsText, out var parsedDigits)
            ? Math.Clamp(parsedDigits, 6, 8)
            : 6;
        var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
        if (options.TryGetValue("time", out var timeText) && long.TryParse(timeText, out var parsedTime))
        {
            timestamp = parsedTime;
        }

        byte[] keyBytes;
        try
        {
            keyBytes = DecodeBase32(secret);
        }
        catch (global::System.FormatException exception)
        {
            return ToolExecutionResult.Fail(exception.Message);
        }

        var counter = timestamp / period;
        var counterBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(counter));
        using var hmac = new HMACSHA1(keyBytes);
        var hash = hmac.ComputeHash(counterBytes);
        var offset = hash[^1] & 0x0f;
        var binary =
            ((hash[offset] & 0x7f) << 24) |
            ((hash[offset + 1] & 0xff) << 16) |
            ((hash[offset + 2] & 0xff) << 8) |
            (hash[offset + 3] & 0xff);
        var modulo = (int)Math.Pow(10, digits);
        var code = (binary % modulo).ToString(new string('0', digits), CultureInfo.InvariantCulture);
        var remaining = period - (timestamp % period);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"Code: {code}",
            $"Digits: {digits}",
            $"Period: {period}s",
            $"Remaining: {remaining}s",
            $"Counter: {counter}"
        }));
    }

    private static byte[] DecodeBase32(string secret)
    {
        const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
        var clean = Regex.Replace(secret.ToUpperInvariant(), @"[\s=\-]", string.Empty);
        if (clean.Length == 0)
        {
            throw new global::System.FormatException("请输入 Base32 密钥。");
        }

        var bytes = new List<byte>();
        var buffer = 0;
        var bitsLeft = 0;
        foreach (var ch in clean)
        {
            var value = alphabet.IndexOf(ch);
            if (value < 0)
            {
                throw new global::System.FormatException("Base32 密钥包含无效字符。");
            }

            buffer = (buffer << 5) | value;
            bitsLeft += 5;
            if (bitsLeft >= 8)
            {
                bytes.Add((byte)((buffer >> (bitsLeft - 8)) & 0xff));
                bitsLeft -= 8;
            }
        }

        return bytes.ToArray();
    }

    private static ToolExecutionResult Password(string input)
    {
        var length = int.TryParse(input.Trim(), out var requested) ? Math.Clamp(requested, 8, 128) : 24;
        const string alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*";
        return ToolExecutionResult.Success(RandomNumberGenerator.GetString(alphabet, length));
    }

    private static string Ulid()
    {
        var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        Span<byte> bytes = stackalloc byte[16];
        RandomNumberGenerator.Fill(bytes);
        bytes[0] = (byte)(time >> 40);
        bytes[1] = (byte)(time >> 32);
        bytes[2] = (byte)(time >> 24);
        bytes[3] = (byte)(time >> 16);
        bytes[4] = (byte)(time >> 8);
        bytes[5] = (byte)time;
        const string crockford = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
        var value = global::System.Numerics.BigInteger.Abs(new global::System.Numerics.BigInteger(bytes, isUnsigned: true, isBigEndian: true));
        var chars = new char[26];
        for (var i = 25; i >= 0; i--)
        {
            chars[i] = crockford[(int)(value % 32)];
            value /= 32;
        }
        return new string(chars);
    }

    private static ToolExecutionResult GenerateIds(string input, bool ulid)
    {
        var count = int.TryParse(input.Trim(), out var requested)
            ? Math.Clamp(requested, 1, 100)
            : 1;
        var values = Enumerable.Range(0, count)
            .Select(_ => ulid ? Ulid() : $"{Guid.NewGuid():D}");
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, values));
    }

    private static ToolExecutionResult RandomText(string input)
    {
        var length = int.TryParse(input.Trim(), out var requested) ? Math.Clamp(requested, 1, 4096) : 32;
        return ToolExecutionResult.Success(RandomNumberGenerator.GetString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", length));
    }

    private static ToolExecutionResult Timestamp(string input)
    {
        if (long.TryParse(input.Trim(), out var unix))
        {
            var seconds = unix > 9_999_999_999 ? unix / 1000 : unix;
            return ToolExecutionResult.Success(DateTimeOffset.FromUnixTimeSeconds(seconds).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture));
        }

        return ToolExecutionResult.Success($"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss zzz}{Environment.NewLine}Unix seconds: {DateTimeOffset.Now.ToUnixTimeSeconds()}");
    }

    private static ToolExecutionResult NumberBase(string input)
    {
        var text = input.Trim();
        var value = text.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
            ? Convert.ToInt64(text[2..], 16)
            : Convert.ToInt64(text);
        return ToolExecutionResult.Success($"DEC: {value}{Environment.NewLine}HEX: 0x{value:X}{Environment.NewLine}BIN: {Convert.ToString(value, 2)}{Environment.NewLine}OCT: {Convert.ToString(value, 8)}");
    }

    private static ToolExecutionResult Unit(string id, string input)
    {
        var value = double.TryParse(input.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed) ? parsed : 1d;
        return id switch
        {
            "length_converter" => ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
            {
                $"m: {value:0.####}",
                $"km: {value / 1000:0.####}",
                $"cm: {value * 100:0.####}",
                $"mm: {value * 1000:0.####}",
                $"inch: {value / 0.0254:0.####}",
                $"ft: {value / 0.3048:0.####}",
                $"mile: {value / 1609.344:0.####}"
            })),
            "weight_converter" => ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
            {
                $"kg: {value:0.####}",
                $"g: {value * 1000:0.####}",
                $"t: {value / 1000:0.####}",
                $"lb: {value * 2.2046226218:0.####}",
                $"oz: {value * 35.27396195:0.####}"
            })),
            "temperature_converter" => ToolExecutionResult.Success($"C: {value:0.###}{Environment.NewLine}F: {(value * 9 / 5) + 32:0.###}{Environment.NewLine}K: {value + 273.15:0.###}"),
            "speed_converter" => ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
            {
                $"m/s: {value:0.####}",
                $"km/h: {value * 3.6:0.####}",
                $"mph: {value * 2.2369362921:0.####}",
                $"kn: {value * 1.9438444924:0.####}"
            })),
            "area_converter" => ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
            {
                $"m²: {value:0.####}",
                $"km²: {value / 1_000_000:0.####}",
                $"ha: {value / 10_000:0.####}",
                $"亩: {value / 666.6666667:0.####}",
                $"ft²: {value * 10.763910417:0.####}"
            })),
            "volume_converter" => ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
            {
                $"m³: {value:0.####}",
                $"L: {value * 1000:0.####}",
                $"mL: {value * 1_000_000:0.####}",
                $"US gal: {value * 264.17205236:0.####}"
            })),
            "data_size_converter" => ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
            {
                $"B: {value:0.###}",
                $"KiB: {value / 1024:0.###}",
                $"MiB: {value / 1024 / 1024:0.###}",
                $"GiB: {value / 1024 / 1024 / 1024:0.###}",
                $"KB: {value / 1000:0.###}",
                $"MB: {value / 1000 / 1000:0.###}",
                $"GB: {value / 1000 / 1000 / 1000:0.###}"
            })),
            "time_duration_converter" => ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
            {
                $"seconds: {value:0.####}",
                $"minutes: {value / 60:0.####}",
                $"hours: {value / 3600:0.####}",
                $"days: {value / 86400:0.####}",
                $"weeks: {value / 604800:0.####}"
            })),
            _ => ToolExecutionResult.Success($"Input: {value:0.###}{Environment.NewLine}Metric x1000: {value * 1000:0.###}{Environment.NewLine}Metric /1000: {value / 1000:0.###}")
        };
    }

    private static ToolExecutionResult Calculator(string id, string input)
    {
        var numbers = Numbers(input).ToArray();
        if (id == "percentage_calculator")
        {
            var part = NumberAt(numbers, 0, 25);
            var whole = NumberAt(numbers, 1, 100);
            return ToolExecutionResult.Success($"{part / whole * 100:0.####}%");
        }

        if (id == "percentage_change_calculator")
        {
            var oldValue = NumberAt(numbers, 0, 100);
            var newValue = NumberAt(numbers, 1, 120);
            return ToolExecutionResult.Success($"{(newValue - oldValue) / oldValue * 100:0.####}%");
        }

        if (id == "discount_calculator")
        {
            var price = NumberAt(numbers, 0, 100);
            var discount = NumberAt(numbers, 1, 80);
            var final = price * discount / 100;
            return ToolExecutionResult.Success($"Final: {final:0.00}{Environment.NewLine}Saved: {price - final:0.00}");
        }

        if (id == "tax_calculator")
        {
            var price = NumberAt(numbers, 0, 100);
            var rate = NumberAt(numbers, 1, 13) / 100;
            return ToolExecutionResult.Success($"Tax: {price * rate:0.00}{Environment.NewLine}With tax: {price * (1 + rate):0.00}{Environment.NewLine}Before tax: {price / (1 + rate):0.00}");
        }

        if (id == "ratio_simplifier")
        {
            var a = (long)NumberAt(numbers, 0, 16);
            var b = (long)NumberAt(numbers, 1, 9);
            var gcd = Gcd(Math.Abs(a), Math.Abs(b));
            return ToolExecutionResult.Success($"{a / gcd}:{b / gcd}");
        }

        if (id is "average_calculator" or "median_calculator")
        {
            var values = numbers.Length == 0 ? [0d] : numbers.Order().ToArray();
            var average = values.Average();
            var median = values.Length % 2 == 1
                ? values[values.Length / 2]
                : (values[(values.Length / 2) - 1] + values[values.Length / 2]) / 2;
            return ToolExecutionResult.Success($"Count: {values.Length}{Environment.NewLine}Sum: {values.Sum():0.####}{Environment.NewLine}Average: {average:0.####}{Environment.NewLine}Median: {median:0.####}{Environment.NewLine}Min: {values.First():0.####}{Environment.NewLine}Max: {values.Last():0.####}");
        }

        if (id == "gcd_lcm_calculator")
        {
            var values = numbers.Select(value => Math.Abs((long)value)).Where(value => value > 0).DefaultIfEmpty(1).ToArray();
            var gcd = values.Aggregate(Gcd);
            var lcm = values.Aggregate((left, right) => left / Gcd(left, right) * right);
            return ToolExecutionResult.Success($"GCD: {gcd}{Environment.NewLine}LCM: {lcm}");
        }

        if (id is "permutation_calculator" or "combination_calculator")
        {
            var n = (int)NumberAt(numbers, 0, 10);
            var r = (int)NumberAt(numbers, 1, 3);
            var permutation = Factorial(n) / Factorial(Math.Max(0, n - r));
            var combination = permutation / Factorial(r);
            return ToolExecutionResult.Success(id == "permutation_calculator" ? $"P({n},{r}) = {permutation}" : $"C({n},{r}) = {combination}");
        }

        if (id == "simple_interest_calculator")
        {
            var principal = NumberAt(numbers, 0, 10000);
            var rate = NumberAt(numbers, 1, 3.5) / 100;
            var years = NumberAt(numbers, 2, 1);
            var interest = principal * rate * years;
            return ToolExecutionResult.Success($"Interest: {interest:0.00}{Environment.NewLine}Total: {principal + interest:0.00}");
        }

        if (id == "compound_interest_calculator")
        {
            var principal = NumberAt(numbers, 0, 10000);
            var rate = NumberAt(numbers, 1, 3.5) / 100;
            var years = NumberAt(numbers, 2, 3);
            var times = NumberAt(numbers, 3, 12);
            var total = principal * Math.Pow(1 + rate / times, times * years);
            return ToolExecutionResult.Success($"Future value: {total:0.00}{Environment.NewLine}Interest: {total - principal:0.00}");
        }

        if (id == "loan_emi_calculator")
        {
            var principal = NumberAt(numbers, 0, 100000);
            var annualRate = NumberAt(numbers, 1, 4.5) / 100 / 12;
            var months = (int)NumberAt(numbers, 2, 360);
            var emi = principal * annualRate * Math.Pow(1 + annualRate, months) / (Math.Pow(1 + annualRate, months) - 1);
            return ToolExecutionResult.Success($"Monthly payment: {emi:0.00}{Environment.NewLine}Total: {emi * months:0.00}{Environment.NewLine}Interest: {emi * months - principal:0.00}");
        }

        var expression = input.Trim();
        var allowed = Regex.Replace(expression, @"[^0-9+\-*/(). ]", string.Empty);
        var result = new global::System.Data.DataTable().Compute(allowed, null);
        return ToolExecutionResult.Success(Convert.ToString(result, CultureInfo.InvariantCulture) ?? string.Empty);
    }

    private static ToolExecutionResult Bmi(string input)
    {
        var numbers = Numbers(input).ToArray();
        var weight = NumberAt(numbers, 0, 70);
        var height = NumberAt(numbers, 1, 1.75);
        if (height > 3) height /= 100;
        var bmi = weight / (height * height);
        return ToolExecutionResult.Success($"BMI: {bmi:0.0}");
    }

    private static ToolExecutionResult RegexMatch(string input)
    {
        var parts = input.Replace("\r\n", "\n").Split('\n', 2);
        var pattern = parts.ElementAtOrDefault(0) ?? string.Empty;
        var text = parts.ElementAtOrDefault(1) ?? string.Empty;
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, Regex.Matches(text, pattern).Select(match => match.Value)));
    }

    private static ToolExecutionResult Case(string input)
    {
        var words = Regex.Matches(input, @"[A-Za-z0-9]+").Select(match => match.Value).ToArray();
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            input.ToUpperInvariant(),
            input.ToLowerInvariant(),
            string.Join("-", words).ToLowerInvariant(),
            string.Join("_", words).ToLowerInvariant(),
            string.Concat(words.Select(word => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(word.ToLowerInvariant())))
        }));
    }

    private static ToolExecutionResult Chinese(string input)
    {
        var text = input.Trim();
        if (string.IsNullOrWhiteSpace(text))
        {
            return ToolExecutionResult.Fail("请输入需要转换的中文文本。");
        }

        var simplified = ConvertChinese(text, toTraditional: false);
        var traditional = ConvertChinese(text, toTraditional: true);
        return ToolExecutionResult.Success($"简体：{simplified}{Environment.NewLine}{Environment.NewLine}繁体：{traditional}");
    }

    private static ToolExecutionResult TextDiff(string input)
    {
        var parts = input.Replace("\r\n", "\n").Split("\n---\n", 2, StringSplitOptions.None);
        if (parts.Length < 2)
        {
            return ToolExecutionResult.Fail("请用单独一行 --- 分隔左右两段文本。");
        }

        var left = parts[0].Split('\n');
        var right = parts[1].Split('\n');
        var dp = new int[left.Length + 1, right.Length + 1];
        for (var i = left.Length - 1; i >= 0; i--)
        {
            for (var j = right.Length - 1; j >= 0; j--)
            {
                dp[i, j] = string.Equals(left[i], right[j], StringComparison.Ordinal)
                    ? dp[i + 1, j + 1] + 1
                    : Math.Max(dp[i + 1, j], dp[i, j + 1]);
            }
        }

        var output = new List<string>();
        var x = 0;
        var y = 0;
        while (x < left.Length && y < right.Length)
        {
            if (string.Equals(left[x], right[y], StringComparison.Ordinal))
            {
                output.Add("  " + left[x]);
                x++;
                y++;
            }
            else if (dp[x + 1, y] >= dp[x, y + 1])
            {
                output.Add("- " + left[x++]);
            }
            else
            {
                output.Add("+ " + right[y++]);
            }
        }

        while (x < left.Length)
        {
            output.Add("- " + left[x++]);
        }

        while (y < right.Length)
        {
            output.Add("+ " + right[y++]);
        }

        return ToolExecutionResult.Success(string.Join(Environment.NewLine, output));
    }

    private static ToolExecutionResult Deduplicate(string input)
    {
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input).Distinct(StringComparer.OrdinalIgnoreCase)));
    }

    private static ToolExecutionResult SortLines(string input)
    {
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input).Order(StringComparer.CurrentCulture)));
    }

    private static ToolExecutionResult FilterLines(string input)
    {
        var lines = Lines(input).ToArray();
        var keyword = lines.FirstOrDefault() ?? string.Empty;
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, lines.Skip(1).Where(line => line.Contains(keyword, StringComparison.OrdinalIgnoreCase))));
    }

    private static ToolExecutionResult TextStatistics(string input)
    {
        var lines = Lines(input).ToArray();
        var words = Regex.Matches(input, @"\p{L}+|\p{N}+").Count;
        return ToolExecutionResult.Success($"Characters: {input.Length}{Environment.NewLine}Lines: {lines.Length}{Environment.NewLine}Words/Tokens: {words}{Environment.NewLine}Non-empty lines: {lines.Count(line => !string.IsNullOrWhiteSpace(line))}");
    }

    private static ToolExecutionResult Delimiters(string input)
    {
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, SplitTokens(input)));
    }

    private static ToolExecutionResult Column(string input)
    {
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input).Select(line => SplitTokens(line).FirstOrDefault() ?? string.Empty)));
    }

    private static ToolExecutionResult CsvJson(string input)
    {
        var lines = Lines(input).ToArray();
        if (lines.Length < 2) return ToolExecutionResult.Success("[]");
        var headers = SplitCsv(lines[0]).ToArray();
        var rows = lines.Skip(1).Select(line =>
        {
            var cells = SplitCsv(line).ToArray();
            return headers.Select((header, index) => new KeyValuePair<string, string>(header, cells.ElementAtOrDefault(index) ?? string.Empty))
                .ToDictionary(pair => pair.Key, pair => pair.Value);
        });
        return ToolExecutionResult.Success(JsonSerializer.Serialize(rows, new JsonSerializerOptions { WriteIndented = true }));
    }

    private static ToolExecutionResult KeyValueToJson(string input)
    {
        var pairs = Lines(input)
            .Select(line => line.Split(['=', ':'], 2))
            .Where(parts => parts.Length == 2)
            .ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim(), StringComparer.OrdinalIgnoreCase);
        if (pairs.Count == 0)
        {
            pairs = SplitTokens(input)
                .Select(token => token.Split('=', 2))
                .Where(parts => parts.Length == 2)
                .ToDictionary(parts => parts[0].Trim(), parts => Uri.UnescapeDataString(parts[1].Trim()), StringComparer.OrdinalIgnoreCase);
        }

        return ToolExecutionResult.Success(JsonSerializer.Serialize(pairs, new JsonSerializerOptions { WriteIndented = true }));
    }

    private static ToolExecutionResult MarkdownTable(string input)
    {
        var rows = Lines(input)
            .Where(line => line.Contains('|'))
            .Select(ParseMarkdownRow)
            .Where(row => row.Length > 0)
            .ToList();
        if (rows.Count == 0)
        {
            return ToolExecutionResult.Fail("请输入 Markdown 表格内容。");
        }

        if (rows.Count > 1 && IsMarkdownSeparatorRow(rows[1]))
        {
            rows.RemoveAt(1);
        }

        var columnCount = rows.Max(row => row.Length);
        var normalized = rows
            .Select(row => row.Concat(Enumerable.Repeat(string.Empty, columnCount - row.Length)).ToArray())
            .ToList();
        var widths = Enumerable.Range(0, columnCount)
            .Select(index => Math.Max(3, normalized.Max(row => row[index].Length)))
            .ToArray();

        string FormatRow(IReadOnlyList<string> row) =>
            "| " + string.Join(" | ", row.Select((cell, index) => cell.PadRight(widths[index]))) + " |";

        var output = new List<string> { FormatRow(normalized[0]) };
        output.Add("| " + string.Join(" | ", widths.Select(width => new string('-', width))) + " |");
        output.AddRange(normalized.Skip(1).Select(FormatRow));
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, output));
    }

    private static string[] ParseMarkdownRow(string line)
    {
        var trimmed = line.Trim();
        if (trimmed.StartsWith('|'))
        {
            trimmed = trimmed[1..];
        }

        if (trimmed.EndsWith('|'))
        {
            trimmed = trimmed[..^1];
        }

        return trimmed.Split('|').Select(cell => cell.Trim()).ToArray();
    }

    private static bool IsMarkdownSeparatorRow(IEnumerable<string> row)
    {
        return row.All(cell => Regex.IsMatch(cell.Trim(), "^:?-{3,}:?$"));
    }

    private static ToolExecutionResult MarkdownPreview(string input)
    {
        var lines = input.Replace("\r\n", "\n").Split('\n');
        var headings = lines
            .Where(line => Regex.IsMatch(line, @"^\s{0,3}#{1,6}\s+"))
            .Select(line => line.Trim())
            .ToArray();
        var links = Regex.Matches(input, @"\[[^\]]+\]\((?<url>[^)]+)\)")
            .Select(match => match.Groups["url"].Value)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .Take(20)
            .ToArray();
        var plain = Regex.Replace(input, @"!\[[^\]]*\]\([^)]+\)", string.Empty);
        plain = Regex.Replace(plain, @"\[[^\]]+\]\(([^)]+)\)", "$1");
        plain = Regex.Replace(plain, @"[`*_>#~\-]+", " ");
        plain = Regex.Replace(plain, @"\s+", " ").Trim();
        var normalized = string.Join(Environment.NewLine, lines.Select(line =>
        {
            var trimmedEnd = line.TrimEnd();
            return Regex.Replace(trimmedEnd, @"^\s*[-*+]\s+", "- ");
        }));

        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"Headings: {headings.Length}",
            $"Links: {links.Length}",
            $"Characters: {input.Length}",
            $"Words: {Regex.Matches(plain, @"[\p{L}\p{N}_]+").Count}",
            string.Empty,
            "Headings:",
            headings.Length == 0 ? "(none)" : string.Join(Environment.NewLine, headings),
            string.Empty,
            "Links:",
            links.Length == 0 ? "(none)" : string.Join(Environment.NewLine, links),
            string.Empty,
            "Plain preview:",
            plain.Length > 600 ? plain[..600] + "..." : plain,
            string.Empty,
            "Normalized:",
            normalized
        }));
    }

    private static ToolExecutionResult JsonPaths(string input)
    {
        using var document = JsonDocument.Parse(input);
        var paths = new List<string>();
        VisitJson(document.RootElement, "$", paths);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, paths.Take(300)));
    }

    private static void VisitJson(JsonElement element, string path, ICollection<string> paths)
    {
        switch (element.ValueKind)
        {
            case JsonValueKind.Object:
                var propertyCount = 0;
                foreach (var property in element.EnumerateObject())
                {
                    propertyCount++;
                    VisitJson(property.Value, $"{path}.{property.Name}", paths);
                }

                if (propertyCount == 0)
                {
                    paths.Add($"{path} = {{}}");
                }
                break;
            case JsonValueKind.Array:
                var index = 0;
                foreach (var item in element.EnumerateArray())
                {
                    VisitJson(item, $"{path}[{index++}]", paths);
                }

                if (index == 0)
                {
                    paths.Add($"{path} = []");
                }
                break;
            case JsonValueKind.String:
                paths.Add($"{path} = \"{element.GetString()}\"");
                break;
            case JsonValueKind.Number:
            case JsonValueKind.True:
            case JsonValueKind.False:
            case JsonValueKind.Null:
                paths.Add($"{path} = {element.GetRawText()}");
                break;
            default:
                paths.Add(path);
                break;
        }
    }

    private static ToolExecutionResult HashManifest(string input)
    {
        var targets = Lines(input).ToArray();
        if (targets.Length == 0)
        {
            return ToolExecutionResult.Fail("请输入文件路径列表，或输入要生成清单的文本行。");
        }

        var rows = targets.Select(target =>
        {
            var normalized = target.Trim().Trim('"');
            var hash = File.Exists(normalized)
                ? HashFile(normalized)
                : Hex(SHA256.HashData(Encoding.UTF8.GetBytes(target)));
            return $"{hash}  {target}";
        });
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, rows));
    }

    private static ToolExecutionResult VerifyHashManifest(string input)
    {
        var rows = Lines(input).ToArray();
        if (rows.Length == 0)
        {
            return ToolExecutionResult.Fail("请输入 SHA256 清单，每行格式为：hash  path。");
        }

        var output = new List<string>();
        foreach (var row in rows)
        {
            var match = Regex.Match(row, @"^(?<hash>[a-fA-F0-9]{64})\s+\*?(?<target>.+)$");
            if (!match.Success)
            {
                output.Add($"SKIP  {row}");
                continue;
            }

            var expected = match.Groups["hash"].Value.ToLowerInvariant();
            var target = match.Groups["target"].Value.Trim().Trim('"');
            if (!File.Exists(target) && LooksLikePath(target))
            {
                output.Add($"MISSING  {target}");
                continue;
            }

            var actual = File.Exists(target)
                ? HashFile(target)
                : Hex(SHA256.HashData(Encoding.UTF8.GetBytes(target)));
            output.Add(string.Equals(expected, actual, StringComparison.OrdinalIgnoreCase)
                ? $"OK  {target}"
                : $"MISMATCH  {target}");
        }

        return ToolExecutionResult.Success(string.Join(Environment.NewLine, output));
    }

    private static ToolExecutionResult Sql(string input)
    {
        var keywords = new[] { "select", "from", "where", "group by", "order by", "having", "left join", "right join", "inner join", "join", "limit" };
        var formatted = input.Trim();
        foreach (var keyword in keywords)
        {
            formatted = Regex.Replace(formatted, $@"\s+{Regex.Escape(keyword)}\s+", $"{Environment.NewLine}{keyword.ToUpperInvariant()} ", RegexOptions.IgnoreCase);
        }

        return ToolExecutionResult.Success(formatted.Trim());
    }

    private static ToolExecutionResult ReplaceText(string input)
    {
        var parts = input.Replace("\r\n", "\n").Split('\n', 3);
        return parts.Length < 3
            ? ToolExecutionResult.Fail("第一行写查找文本，第二行写替换文本，第三行开始写正文。")
            : ToolExecutionResult.Success(parts[2].Replace(parts[0], parts[1], StringComparison.Ordinal));
    }

    private static ToolExecutionResult FileNames(string input)
    {
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input).Select(line => Regex.Replace(line.Trim(), @"[\\/:*?""<>|]+", "_"))));
    }

    private static ToolExecutionResult ColorTool(string input)
    {
        if (!TryParseColor(input, out var r, out var g, out var b))
        {
            return ToolExecutionResult.Fail("请输入 #RRGGBB、#RGB 或 R G B 格式的颜色值。");
        }

        var (h, s, l) = RgbToHsl(r, g, b);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"HEX: #{r:X2}{g:X2}{b:X2}",
            $"RGB: {r}, {g}, {b}",
            $"HSL: {h:0.#}, {s:0.#}%, {l:0.#}%"
        }));
    }

    private static ToolExecutionResult ColorContrast(string input)
    {
        var parts = Lines(input).SelectMany(line => Regex.Split(line, @"\s+")).Where(part => !string.IsNullOrWhiteSpace(part)).ToArray();
        if (parts.Length < 2 ||
            !TryParseColor(parts[0], out var fr, out var fg, out var fb) ||
            !TryParseColor(parts[1], out var br, out var bg, out var bb))
        {
            return ToolExecutionResult.Fail("请输入两个颜色值，例如：#111827 #ffffff。");
        }

        var foreground = RelativeLuminance(fr, fg, fb);
        var background = RelativeLuminance(br, bg, bb);
        var lighter = Math.Max(foreground, background);
        var darker = Math.Min(foreground, background);
        var ratio = (lighter + 0.05) / (darker + 0.05);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"Foreground: #{fr:X2}{fg:X2}{fb:X2}",
            $"Background: #{br:X2}{bg:X2}{bb:X2}",
            $"Contrast: {ratio:0.00}:1",
            $"AA normal text: {(ratio >= 4.5 ? "Pass" : "Fail")}",
            $"AA large text: {(ratio >= 3 ? "Pass" : "Fail")}",
            $"AAA normal text: {(ratio >= 7 ? "Pass" : "Fail")}",
            $"AAA large text: {(ratio >= 4.5 ? "Pass" : "Fail")}"
        }));
    }

    private static double RelativeLuminance(byte r, byte g, byte b)
    {
        static double Channel(byte value)
        {
            var normalized = value / 255d;
            return normalized <= 0.04045
                ? normalized / 12.92
                : Math.Pow((normalized + 0.055) / 1.055, 2.4);
        }

        return 0.2126 * Channel(r) + 0.7152 * Channel(g) + 0.0722 * Channel(b);
    }

    private static ToolExecutionResult QrGenerate(string input)
    {
        var payload = string.IsNullOrWhiteSpace(input) ? "YMhut Box" : input.Trim();
        using var generator = new QRCodeGenerator();
        using var data = generator.CreateQrCode(payload, QRCodeGenerator.ECCLevel.Q);
        var svg = new SvgQRCode(data).GetGraphic(4);
        var pngBase64 = Convert.ToBase64String(new PngByteQRCode(data).GetGraphic(12));
        var block = ToolResultBlock.Media(
            ToolResultBlockKind.Image,
            T(null, "二维码预览", "QR code preview"),
            svg,
            string.Empty,
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                ["contentType"] = "image/svg+xml",
                ["displayMode"] = "preview",
                ["rawSvg"] = svg,
                ["pngBase64"] = pngBase64,
                ["payload"] = payload,
                ["suggestedFileName"] = "ymhut-qrcode"
            });
        var document = new ToolResultDocument(
            "qr_generator",
            ToolResultKind.ImagePreview,
            svg,
            [block],
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                ["toolId"] = "qr_generator",
                ["resultKind"] = ToolResultKind.ImagePreview.ToString(),
                ["contentType"] = "image/svg+xml",
                ["generatedAt"] = DateTimeOffset.Now.ToString("O")
            },
            svg,
            string.Empty,
            "ok");
        return ToolExecutionResult.Success(svg, document);
    }

    private static ToolExecutionResult QrScan(string input)
    {
        if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1))
        {
            return ToolExecutionResult.Fail("二维码扫描依赖 Windows 图像解码能力。");
        }

        var path = input.Trim().Trim('"');
        if (!File.Exists(path))
        {
            return ToolExecutionResult.Fail("请输入要扫描的本地图片文件路径。");
        }

        using var bitmap = new Bitmap(path);
        var source = CreateLuminanceSource(bitmap);
        var reader = new MultiFormatReader();
        var result = reader.decode(
            new BinaryBitmap(new HybridBinarizer(source)),
            new Dictionary<DecodeHintType, object>
            {
                [DecodeHintType.TRY_HARDER] = true,
                [DecodeHintType.POSSIBLE_FORMATS] = new[] { BarcodeFormat.QR_CODE }
            });
        return result is null
            ? ToolExecutionResult.Fail("未识别到二维码。")
            : ToolExecutionResult.Success(result.Text);
    }

    private static ToolExecutionResult Archive(string input)
    {
        var commandLine = Lines(input).FirstOrDefault() ?? input;
        var extractMatch = Regex.Match(commandLine, "^extract\\s+(?:\"(?<path>[^\"]+)\"|(?<path>\\S+))(?:\\s+(?:\"(?<dest>[^\"]+)\"|(?<dest>\\S+)))?$", RegexOptions.IgnoreCase);
        if (extractMatch.Success)
        {
            var archivePath = extractMatch.Groups["path"].Value;
            var destination = extractMatch.Groups["dest"].Success
                ? extractMatch.Groups["dest"].Value
                : NextAvailablePath(Path.Combine(Path.GetDirectoryName(archivePath) ?? Environment.CurrentDirectory, Path.GetFileNameWithoutExtension(archivePath)));
            if (!File.Exists(archivePath))
            {
                return ToolExecutionResult.Fail("请输入要解压的 .zip 或 .tar 文件路径。");
            }

            Directory.CreateDirectory(destination);
            if (Path.GetExtension(archivePath).Equals(".zip", StringComparison.OrdinalIgnoreCase))
            {
                ZipFile.ExtractToDirectory(archivePath, destination, overwriteFiles: false);
                return ToolExecutionResult.Success($"已解压到：{destination}");
            }

            if (Path.GetExtension(archivePath).Equals(".tar", StringComparison.OrdinalIgnoreCase))
            {
                TarFile.ExtractToDirectory(archivePath, destination, overwriteFiles: false);
                return ToolExecutionResult.Success($"已解压到：{destination}");
            }

            return ToolExecutionResult.Fail("目前支持解压 .zip 和 .tar。");
        }

        var tarMatch = Regex.Match(commandLine, "^tar\\s+(?:\"(?<path>[^\"]+)\"|(?<path>\\S+))$", RegexOptions.IgnoreCase);
        var path = (tarMatch.Success ? tarMatch.Groups["path"].Value : commandLine).Trim().Trim('"');
        if (Directory.Exists(path))
        {
            var targetPath = NextAvailablePath(Path.Combine(Path.GetDirectoryName(path) ?? Environment.CurrentDirectory, Path.GetFileName(path) + (tarMatch.Success ? ".tar" : ".zip")));
            if (tarMatch.Success)
            {
                TarFile.CreateFromDirectory(path, targetPath, includeBaseDirectory: false);
            }
            else
            {
                ZipFile.CreateFromDirectory(path, targetPath, CompressionLevel.Optimal, includeBaseDirectory: false);
            }

            return ToolExecutionResult.Success($"已创建归档：{targetPath}");
        }

        if (File.Exists(path) && Path.GetExtension(path).Equals(".zip", StringComparison.OrdinalIgnoreCase))
        {
            using var archive = ZipFile.OpenRead(path);
            var entries = archive.Entries
                .Take(80)
                .Select(entry => $"{entry.FullName}\t{entry.Length} bytes");
            return ToolExecutionResult.Success(string.Join(Environment.NewLine, entries));
        }

        if (File.Exists(path) && Path.GetExtension(path).Equals(".tar", StringComparison.OrdinalIgnoreCase))
        {
            using var stream = File.OpenRead(path);
            using var reader = new TarReader(stream);
            var entries = new List<string>();
            TarEntry? entry;
            while ((entry = reader.GetNextEntry()) is not null && entries.Count < 80)
            {
                entries.Add($"{entry.Name}\t{entry.Length} bytes");
            }

            return ToolExecutionResult.Success(string.Join(Environment.NewLine, entries));
        }

        if (File.Exists(path))
        {
            var targetPath = NextAvailablePath(Path.ChangeExtension(path, tarMatch.Success ? ".tar" : ".zip"));
            if (tarMatch.Success)
            {
                using var stream = File.Create(targetPath);
                using var writer = new TarWriter(stream, leaveOpen: false);
                writer.WriteEntry(fileName: path, entryName: Path.GetFileName(path));
            }
            else
            {
                using var archive = ZipFile.Open(targetPath, ZipArchiveMode.Create);
                archive.CreateEntryFromFile(path, Path.GetFileName(path), CompressionLevel.Optimal);
            }

            return ToolExecutionResult.Success($"已创建归档：{targetPath}");
        }

        return ToolExecutionResult.Fail("请输入文件、目录、.zip/.tar 归档路径，或使用 extract/tar 命令。");
    }

    private static ToolExecutionResult ImageTool(string input)
    {
        if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1))
        {
            return ToolExecutionResult.Fail("图片处理依赖 Windows 图像解码能力。");
        }

        var commandLine = Lines(input).FirstOrDefault() ?? input;
        var resizeMatch = Regex.Match(commandLine, "^resize\\s+(?:\"(?<path>[^\"]+)\"|(?<path>\\S+))\\s+(?<width>\\d+)\\s+(?<height>\\d+)$", RegexOptions.IgnoreCase);
        if (resizeMatch.Success)
        {
            var resizePath = resizeMatch.Groups["path"].Value;
            var width = int.Parse(resizeMatch.Groups["width"].Value, CultureInfo.InvariantCulture);
            var height = int.Parse(resizeMatch.Groups["height"].Value, CultureInfo.InvariantCulture);
            return ResizeImage(resizePath, width, height);
        }

        var cropMatch = Regex.Match(commandLine, "^crop\\s+(?:\"(?<path>[^\"]+)\"|(?<path>\\S+))\\s+(?<x>\\d+)\\s+(?<y>\\d+)\\s+(?<width>\\d+)\\s+(?<height>\\d+)$", RegexOptions.IgnoreCase);
        if (cropMatch.Success)
        {
            var cropPath = cropMatch.Groups["path"].Value;
            var rect = new Rectangle(
                int.Parse(cropMatch.Groups["x"].Value, CultureInfo.InvariantCulture),
                int.Parse(cropMatch.Groups["y"].Value, CultureInfo.InvariantCulture),
                int.Parse(cropMatch.Groups["width"].Value, CultureInfo.InvariantCulture),
                int.Parse(cropMatch.Groups["height"].Value, CultureInfo.InvariantCulture));
            return CropImage(cropPath, rect);
        }

        var convertMatch = Regex.Match(commandLine, "^convert\\s+(?:\"(?<path>[^\"]+)\"|(?<path>\\S+))\\s+(?<format>png|jpg|jpeg|bmp)$", RegexOptions.IgnoreCase);
        if (convertMatch.Success)
        {
            return ConvertImage(convertMatch.Groups["path"].Value, convertMatch.Groups["format"].Value);
        }

        var imagePath = commandLine.Trim().Trim('"');
        if (!File.Exists(imagePath))
        {
            return ToolExecutionResult.Fail("请输入本地图片路径，或使用 resize / crop / convert 命令。");
        }

        using var image = Image.FromFile(imagePath);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"Path: {imagePath}",
            $"Width: {image.Width}",
            $"Height: {image.Height}",
            $"Pixel format: {image.PixelFormat}",
            $"Raw format: {image.RawFormat}"
        }));
    }

    private static ToolExecutionResult ImageMetadata(string input)
    {
        if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1))
        {
            return ToolExecutionResult.Fail("图片元数据读取依赖 Windows 图像解码能力。");
        }

        var imagePath = (Lines(input).FirstOrDefault() ?? input).Trim().Trim('"');
        if (!File.Exists(imagePath))
        {
            return ToolExecutionResult.Fail("请输入本地图片路径，或使用文件选择器。");
        }

        var info = new FileInfo(imagePath);
        using var image = Image.FromFile(imagePath);
        var rows = new List<string>
        {
            $"Path: {imagePath}",
            $"File name: {info.Name}",
            $"File size: {info.Length} bytes",
            $"Width: {image.Width}",
            $"Height: {image.Height}",
            $"Pixel format: {image.PixelFormat}",
            $"Raw format: {ImageFormatName(image.RawFormat)}",
            $"Horizontal resolution: {image.HorizontalResolution:0.##} dpi",
            $"Vertical resolution: {image.VerticalResolution:0.##} dpi",
            $"Metadata tags: {image.PropertyIdList.Length}"
        };
        foreach (var property in image.PropertyItems.Take(16))
        {
            rows.Add($"EXIF 0x{property.Id:X4}: type {property.Type}, {property.Len} bytes");
        }

        return ToolExecutionResult.Success(string.Join(Environment.NewLine, rows));
    }

    [SupportedOSPlatform("windows6.1")]
    private static string ImageFormatName(ImageFormat format)
    {
        if (format.Guid == ImageFormat.Png.Guid) return "PNG";
        if (format.Guid == ImageFormat.Jpeg.Guid) return "JPEG";
        if (format.Guid == ImageFormat.Bmp.Guid) return "BMP";
        if (format.Guid == ImageFormat.Gif.Guid) return "GIF";
        if (format.Guid == ImageFormat.Tiff.Guid) return "TIFF";
        if (format.Guid == ImageFormat.Icon.Guid) return "ICO";
        return format.ToString();
    }

    private static ToolExecutionResult ResizeImage(string path, int width, int height)
    {
        if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1))
        {
            return ToolExecutionResult.Fail("图片处理依赖 Windows 图像解码能力。");
        }

        if (!File.Exists(path))
        {
            return ToolExecutionResult.Fail("请输入本地图片文件路径。");
        }

        using var source = Image.FromFile(path);
        using var target = new Bitmap(width, height);
        using (var graphics = Graphics.FromImage(target))
        {
            graphics.InterpolationMode = global::System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
            graphics.DrawImage(source, 0, 0, width, height);
        }

        var output = NextAvailablePath(Path.Combine(Path.GetDirectoryName(path) ?? Environment.CurrentDirectory, $"{Path.GetFileNameWithoutExtension(path)}_{width}x{height}.png"));
        target.Save(output, ImageFormat.Png);
        return ToolExecutionResult.Success($"已输出：{output}");
    }

    private static ToolExecutionResult CropImage(string path, Rectangle rectangle)
    {
        if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1))
        {
            return ToolExecutionResult.Fail("图片处理依赖 Windows 图像解码能力。");
        }

        if (!File.Exists(path))
        {
            return ToolExecutionResult.Fail("请输入本地图片文件路径。");
        }

        using var source = new Bitmap(path);
        var safeRectangle = Rectangle.Intersect(rectangle, new Rectangle(0, 0, source.Width, source.Height));
        if (safeRectangle.Width <= 0 || safeRectangle.Height <= 0)
        {
            return ToolExecutionResult.Fail("裁剪区域超出图片范围。");
        }

        using var target = source.Clone(safeRectangle, source.PixelFormat);
        var output = NextAvailablePath(Path.Combine(Path.GetDirectoryName(path) ?? Environment.CurrentDirectory, $"{Path.GetFileNameWithoutExtension(path)}_crop.png"));
        target.Save(output, ImageFormat.Png);
        return ToolExecutionResult.Success($"已输出：{output}");
    }

    private static ToolExecutionResult ConvertImage(string path, string format)
    {
        if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1))
        {
            return ToolExecutionResult.Fail("图片处理依赖 Windows 图像解码能力。");
        }

        if (!File.Exists(path))
        {
            return ToolExecutionResult.Fail("请输入本地图片文件路径。");
        }

        var normalized = format.Equals("jpeg", StringComparison.OrdinalIgnoreCase) ? "jpg" : format.ToLowerInvariant();
        var imageFormat = normalized switch
        {
            "jpg" => ImageFormat.Jpeg,
            "bmp" => ImageFormat.Bmp,
            _ => ImageFormat.Png
        };
        var output = NextAvailablePath(Path.ChangeExtension(path, normalized));
        using var source = Image.FromFile(path);
        source.Save(output, imageFormat);
        return ToolExecutionResult.Success($"已输出：{output}");
    }

    private static ToolExecutionResult AsciiArt(string input)
    {
        if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1))
        {
            return ToolExecutionResult.Fail("ASCII Art 渲染依赖 Windows 字体栅格化能力。");
        }

        var text = Lines(input).FirstOrDefault() ?? input.Trim();
        if (string.IsNullOrWhiteSpace(text))
        {
            return ToolExecutionResult.Fail("请输入要转换的文本。");
        }

        text = text.Length > 48 ? text[..48] : text;
        using var measureBitmap = new Bitmap(1, 1);
        using var measureGraphics = Graphics.FromImage(measureBitmap);
        using var font = new Font("Consolas", 24, FontStyle.Bold, GraphicsUnit.Pixel);
        var size = measureGraphics.MeasureString(text, font);
        var width = Math.Max(1, (int)Math.Ceiling(size.Width) + 8);
        var height = Math.Max(1, (int)Math.Ceiling(size.Height) + 8);
        using var bitmap = new Bitmap(width, height);
        using (var graphics = Graphics.FromImage(bitmap))
        {
            graphics.Clear(Color.Black);
            graphics.TextRenderingHint = global::System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
            graphics.DrawString(text, font, Brushes.White, new PointF(4, 2));
        }

        var rows = new List<string>();
        for (var y = 0; y < bitmap.Height; y += 2)
        {
            var builder = new StringBuilder();
            for (var x = 0; x < bitmap.Width; x += 2)
            {
                var lit = false;
                for (var yy = y; yy < Math.Min(y + 2, bitmap.Height) && !lit; yy++)
                {
                    for (var xx = x; xx < Math.Min(x + 2, bitmap.Width); xx++)
                    {
                        if (bitmap.GetPixel(xx, yy).R > 96)
                        {
                            lit = true;
                            break;
                        }
                    }
                }

                builder.Append(lit ? '#' : ' ');
            }

            var row = builder.ToString().TrimEnd();
            if (row.Length > 0)
            {
                rows.Add(row);
            }
        }

        return ToolExecutionResult.Success(string.Join(Environment.NewLine, rows));
    }

    private static ToolExecutionResult Profanity(string input)
    {
        var parts = input.Replace("\r\n", "\n").Split("\n---\n", 2, StringSplitOptions.None);
        var defaultTerms = new[] { "spam", "scam", "fraud", "广告", "诈骗", "钓鱼", "违禁", "敏感词" };
        var terms = defaultTerms;
        var text = input;
        if (parts.Length == 2)
        {
            terms = Lines(parts[0]).Where(term => !string.IsNullOrWhiteSpace(term)).ToArray();
            text = parts[1];
        }

        var hits = terms
            .Where(term => text.Contains(term, StringComparison.OrdinalIgnoreCase))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToArray();
        var masked = text;
        foreach (var term in hits)
        {
            masked = Regex.Replace(masked, Regex.Escape(term), new string('*', Math.Max(2, term.Length)), RegexOptions.IgnoreCase);
        }

        return hits.Length == 0
            ? ToolExecutionResult.Success("未发现内置敏感词命中。")
            : ToolExecutionResult.Success($"Potential matches: {string.Join(", ", hits)}{Environment.NewLine}{Environment.NewLine}{masked}");
    }

    private static ToolExecutionResult Unicode(string input)
    {
        var escaped = string.Concat(input.Select(ch => ch > 127 ? $"\\u{(int)ch:X4}" : ch.ToString()));
        return ToolExecutionResult.Success($"{escaped}{Environment.NewLine}{Regex.Unescape(input)}");
    }

    private static ToolExecutionResult Punycode(string input)
    {
        var idn = new IdnMapping();
        return ToolExecutionResult.Success(input.Contains("xn--", StringComparison.OrdinalIgnoreCase)
            ? idn.GetUnicode(input)
            : idn.GetAscii(input));
    }

    private static ToolExecutionResult Jwt(string input)
    {
        var parts = input.Trim().Split('.');
        if (parts.Length < 2) return ToolExecutionResult.Fail("JWT 至少需要 header.payload 两段。");
        return ToolExecutionResult.Success($"{DecodeBase64Url(parts[0])}{Environment.NewLine}{Environment.NewLine}{DecodeBase64Url(parts[1])}");
    }

    private static ToolExecutionResult Chunk(string input)
    {
        var items = Lines(input).ToArray();
        return ToolExecutionResult.Success(string.Join(Environment.NewLine + "---" + Environment.NewLine, items.Chunk(5).Select(chunk => string.Join(Environment.NewLine, chunk))));
    }

    private static ToolExecutionResult SetOperation(string input, string operation)
    {
        var parts = input.Replace("\r\n", "\n").Split("\n---\n", 2, StringSplitOptions.None);
        var left = Lines(parts.ElementAtOrDefault(0) ?? string.Empty).ToHashSet(StringComparer.OrdinalIgnoreCase);
        var right = Lines(parts.ElementAtOrDefault(1) ?? string.Empty).ToHashSet(StringComparer.OrdinalIgnoreCase);
        IEnumerable<string> result = operation switch
        {
            "intersection" => left.Intersect(right, StringComparer.OrdinalIgnoreCase),
            "union" => left.Union(right, StringComparer.OrdinalIgnoreCase),
            _ => left.Except(right, StringComparer.OrdinalIgnoreCase)
        };
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, result));
    }

    private static ToolExecutionResult Template(string input)
    {
        var parts = input.Replace("\r\n", "\n").Split("\n---\n", 2, StringSplitOptions.None);
        var template = parts.ElementAtOrDefault(0) ?? string.Empty;
        foreach (var line in Lines(parts.ElementAtOrDefault(1) ?? string.Empty))
        {
            var pair = line.Split('=', 2);
            if (pair.Length == 2)
            {
                template = template.Replace("{{" + pair[0].Trim() + "}}", pair[1].Trim(), StringComparison.OrdinalIgnoreCase);
            }
        }
        return ToolExecutionResult.Success(template);
    }

    private static ToolExecutionResult PathNormalize(string input)
    {
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input).Select(path => Path.GetFullPath(path))));
    }

    private static ToolExecutionResult PathBreakdown(string input)
    {
        var lines = Lines(input).Select(path => $"{Path.GetDirectoryName(path) ?? string.Empty} | {Path.GetFileNameWithoutExtension(path)} | {Path.GetExtension(path)}");
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, lines));
    }

    private static ToolExecutionResult ExtensionExtract(string input)
    {
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, Lines(input).Select(path => Path.GetExtension(path))));
    }

    private static ToolExecutionResult ExtensionStats(string input)
    {
        var counts = Lines(input)
            .Select(path => string.IsNullOrWhiteSpace(Path.GetExtension(path)) ? "(none)" : Path.GetExtension(path).ToLowerInvariant())
            .GroupBy(ext => ext)
            .OrderByDescending(group => group.Count())
            .Select(group => $"{group.Key}: {group.Count()}");
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, counts));
    }

    private static ToolExecutionResult Manifest(string input)
    {
        var rows = Lines(input).Select(line => new
        {
            Value = line,
            Length = line.Length,
            Hash = Hex(SHA256.HashData(Encoding.UTF8.GetBytes(line)))
        });
        return ToolExecutionResult.Success(JsonSerializer.Serialize(rows, new JsonSerializerOptions { WriteIndented = true }));
    }

    private static ToolExecutionResult RenamePreview(string input, bool indexed)
    {
        var lines = Lines(input).ToList();
        if (lines.Count == 0)
        {
            return ToolExecutionResult.Fail("请输入要预览重命名的文件名或路径列表。");
        }

        var prefix = "item_";
        if (lines[0].StartsWith("prefix=", StringComparison.OrdinalIgnoreCase))
        {
            prefix = lines[0][7..].Trim();
            lines.RemoveAt(0);
        }

        var find = string.Empty;
        var replace = string.Empty;
        if (!indexed && lines.Count > 1 && lines[0].Contains("=>", StringComparison.Ordinal))
        {
            var rule = lines[0].Split("=>", 2, StringSplitOptions.TrimEntries);
            find = rule.ElementAtOrDefault(0) ?? string.Empty;
            replace = rule.ElementAtOrDefault(1) ?? string.Empty;
            lines.RemoveAt(0);
        }

        var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var output = new List<string>();
        for (var index = 0; index < lines.Count; index++)
        {
            var source = lines[index].Trim().Trim('"');
            var fileName = Path.GetFileName(source);
            if (string.IsNullOrWhiteSpace(fileName))
            {
                fileName = source;
            }

            var extension = Path.GetExtension(fileName);
            var nextName = indexed
                ? $"{prefix}{index + 1:000}{extension}"
                : string.IsNullOrEmpty(find)
                    ? SanitizeFileName(fileName)
                    : SanitizeFileName(fileName.Replace(find, replace, StringComparison.OrdinalIgnoreCase));
            nextName = EnsureUniqueFileName(nextName, used);
            output.Add($"{fileName} -> {nextName}");
        }

        return ToolExecutionResult.Success(string.Join(Environment.NewLine, output));
    }

    private static ToolExecutionResult MarkdownList(string input)
    {
        var lines = input.Replace("\r\n", "\n")
            .Split('\n')
            .Select(line => line.Trim())
            .Where(line => line.Length > 0)
            .Select(line => Regex.Replace(line, @"^(?:[-*+]|\d+[.)]|[A-Za-z][.)])\s+", string.Empty).Trim())
            .Where(line => line.Length > 0)
            .Select(line => "- " + line);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, lines));
    }

    private static ToolExecutionResult UrlInspect(string input)
    {
        if (!Uri.TryCreate(input.Trim(), UriKind.Absolute, out var uri))
        {
            return ToolExecutionResult.Fail("请输入完整 URL。");
        }

        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"Scheme: {uri.Scheme}",
            $"Host: {uri.Host}",
            $"Port: {uri.Port}",
            $"Path: {uri.AbsolutePath}",
            $"Query: {uri.Query}",
            $"Fragment: {uri.Fragment}"
        }));
    }

    private static async Task<ToolExecutionResult> UrlRedirectTraceAsync(string input, CancellationToken cancellationToken, string language)
    {
        if (!Uri.TryCreate(input.Trim(), UriKind.Absolute, out var uri) ||
            uri.Scheme is not ("http" or "https"))
        {
            return ToolExecutionResult.Fail(T(language, "请输入 http 或 https URL。", "Enter an http or https URL."));
        }

        using var handler = new HttpClientHandler { AllowAutoRedirect = false };
        using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
        var rows = new List<string>();
        var current = uri;
        for (var index = 0; index < 8; index++)
        {
            using var request = new HttpRequestMessage(HttpMethod.Head, current);
            HttpResponseMessage response;
            try
            {
                response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
            }
            catch (HttpRequestException)
            {
                using var fallback = new HttpRequestMessage(HttpMethod.Get, current);
                response = await client.SendAsync(fallback, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
            }

            using (response)
            {
                var location = response.Headers.Location;
                rows.Add($"{index + 1} | {(int)response.StatusCode} {response.ReasonPhrase} | {current}");
                if ((int)response.StatusCode < 300 || (int)response.StatusCode >= 400 || location is null)
                {
                    rows.Add($"Final URL: {current}");
                    rows.Add($"Final status: {(int)response.StatusCode}");
                    return ToolExecutionResult.Success(string.Join(Environment.NewLine, rows));
                }

                current = location.IsAbsoluteUri ? location : new Uri(current, location);
                rows.Add($"Redirect to: {current}");
            }
        }

        rows.Add($"Stopped: redirect limit reached");
        rows.Add($"Last URL: {current}");
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, rows));
    }

    private static ToolExecutionResult DomainExtract(string input)
    {
        var domains = Lines(input)
            .Select(line => Uri.TryCreate(line, UriKind.Absolute, out var uri) ? uri.Host : line)
            .Where(line => line.Contains('.'))
            .Distinct(StringComparer.OrdinalIgnoreCase);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, domains));
    }

    private static ToolExecutionResult Cidr(string input)
    {
        var parts = input.Split(['/', ' '], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (parts.Length == 0 || !IPAddress.TryParse(parts[0], out var ip))
        {
            return ToolExecutionResult.Fail("请输入 IPv4 地址，例如 192.168.1.0/24。");
        }

        var prefix = parts.Length > 1 && int.TryParse(parts[1], out var parsed) ? parsed : 24;
        var bytes = ip.GetAddressBytes();
        if (bytes.Length != 4)
        {
            return ToolExecutionResult.Fail("仅支持 IPv4。");
        }

        var mask = prefix == 0 ? 0u : uint.MaxValue << (32 - prefix);
        var value = BitConverter.ToUInt32(bytes.Reverse().ToArray(), 0);
        var network = value & mask;
        var broadcast = network | ~mask;
        return ToolExecutionResult.Success($"Network: {ToIPv4(network)}{Environment.NewLine}Broadcast: {ToIPv4(broadcast)}{Environment.NewLine}Mask: {ToIPv4(mask)}{Environment.NewLine}Hosts: {Math.Max(0, (long)Math.Pow(2, 32 - prefix) - 2)}");
    }

    private static ToolExecutionResult PairZip(string input)
    {
        var parts = input.Replace("\r\n", "\n").Split("\n---\n", 2, StringSplitOptions.None);
        var left = Lines(parts.ElementAtOrDefault(0) ?? string.Empty).ToArray();
        var right = Lines(parts.ElementAtOrDefault(1) ?? string.Empty).ToArray();
        var zipped = left.Zip(right, (a, b) => $"{a}\t{b}");
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, zipped));
    }

    private static ToolExecutionResult DateTool(string id, string input)
    {
        var dates = Regex.Matches(input, @"\d{4}[-/]\d{1,2}[-/]\d{1,2}")
            .Select(match => DateOnly.TryParse(match.Value.Replace('/', '-'), out var parsed) ? parsed : (DateOnly?)null)
            .Where(date => date.HasValue)
            .Select(date => date!.Value)
            .ToArray();
        var first = dates.ElementAtOrDefault(0);
        if (first == default)
        {
            first = DateOnly.FromDateTime(DateTime.Today);
        }
        var second = dates.ElementAtOrDefault(1);
        if (second == default)
        {
            second = DateOnly.FromDateTime(DateTime.Today);
        }

        return id switch
        {
            "date_difference_calculator" => ToolExecutionResult.Success($"{Math.Abs(second.DayNumber - first.DayNumber)} days{Environment.NewLine}{PeriodBetween(first < second ? first : second, first < second ? second : first)}"),
            "workday_calculator" => ToolExecutionResult.Success($"{CountWorkdays(first < second ? first : second, first < second ? second : first)} workdays"),
            "age_calculator" => ToolExecutionResult.Success($"{PeriodBetween(first, DateOnly.FromDateTime(DateTime.Today))}"),
            _ => ToolExecutionResult.Success(input)
        };
    }

    private static ToolExecutionResult Cron(string input)
    {
        var parts = input.Split([' ', '\t'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        return parts.Length >= 5
            ? ToolExecutionResult.Success($"Cron OK: {string.Join(" ", parts.Take(5))}")
            : ToolExecutionResult.Fail("请输入五段 Cron 表达式。");
    }

    private static ToolExecutionResult Lookup(string id, string input)
    {
        var table = id switch
        {
            "mime_lookup" => new[]
            {
                "text/plain - Plain text",
                "text/html - HTML document",
                "text/css - Cascading Style Sheets",
                "application/json - JSON document",
                "application/xml - XML document",
                "application/pdf - PDF document",
                "image/png - PNG image",
                "image/jpeg - JPEG image",
                "image/svg+xml - SVG image",
                "application/zip - ZIP archive"
            },
            "http_status_lookup" => new[]
            {
                "100 Continue",
                "200 OK",
                "201 Created",
                "204 No Content",
                "301 Moved Permanently",
                "302 Found",
                "304 Not Modified",
                "400 Bad Request",
                "401 Unauthorized",
                "403 Forbidden",
                "404 Not Found",
                "429 Too Many Requests",
                "500 Internal Server Error",
                "502 Bad Gateway",
                "503 Service Unavailable"
            },
            "port_lookup" => new[]
            {
                "20/21 - FTP 文件传输服务",
                "22 - SSH 安全远程登录",
                "25 - SMTP 邮件发送",
                "53 - DNS 域名解析",
                "80 - HTTP 网页服务",
                "110 - POP3 邮件接收",
                "143 - IMAP 邮件同步",
                "443 - HTTPS 加密网页服务",
                "3306 - MySQL 数据库",
                "3389 - RDP 远程桌面",
                "5432 - PostgreSQL 数据库",
                "6379 - Redis 缓存服务"
            },
            "dns_record_lookup" => new[]
            {
                "A - IPv4 address",
                "AAAA - IPv6 address",
                "CNAME - Canonical name alias",
                "MX - Mail exchanger",
                "TXT - Text metadata",
                "NS - Authoritative name server",
                "SOA - Zone authority",
                "CAA - Certificate authority authorization",
                "SRV - Service locator"
            },
            "unicode_block_lookup" => new[]
            {
                "U+0000..U+007F Basic Latin",
                "U+0080..U+00FF Latin-1 Supplement",
                "U+2000..U+206F General Punctuation",
                "U+3040..U+309F Hiragana",
                "U+30A0..U+30FF Katakana",
                "U+4E00..U+9FFF CJK Unified Ideographs",
                "U+1F300..U+1FAFF Symbols and Pictographs"
            },
            "charset_lookup" => new[]
            {
                "UTF-8 - Unicode variable-width encoding",
                "UTF-16LE - Unicode little-endian",
                "GB18030 - Chinese national standard",
                "GBK - Simplified Chinese legacy encoding",
                "Big5 - Traditional Chinese legacy encoding",
                "Shift_JIS - Japanese legacy encoding",
                "ISO-8859-1 - Western European legacy encoding"
            },
            "timezone_abbr_lookup" => new[]
            {
                "UTC +00:00 Coordinated Universal Time",
                "CST +08:00 China Standard Time",
                "JST +09:00 Japan Standard Time",
                "EST -05:00 Eastern Standard Time",
                "EDT -04:00 Eastern Daylight Time",
                "PST -08:00 Pacific Standard Time",
                "PDT -07:00 Pacific Daylight Time"
            },
            "regex_preset_lookup" => new[]
            {
                @"整数: ^-?\d+$",
                @"邮箱: ^[^\s@]+@[^\s@]+\.[^\s@]+$",
                @"IPv4: ^(?:\d{1,3}\.){3}\d{1,3}$",
                @"URL: ^https?://\S+$",
                @"中文: [\u4e00-\u9fff]+",
                @"空白行: ^\s*$"
            },
            "magic_number_lookup" => new[]
            {
                "PNG: 89 50 4E 47 0D 0A 1A 0A",
                "JPEG: FF D8 FF",
                "GIF: 47 49 46 38",
                "PDF: 25 50 44 46",
                "ZIP: 50 4B 03 04",
                "7z: 37 7A BC AF 27 1C",
                "RAR: 52 61 72 21 1A 07"
            },
            _ => [input]
        };

        var query = input.Trim();
        var rows = string.IsNullOrWhiteSpace(query)
            ? table
            : table.Where(row => row.Contains(query, StringComparison.OrdinalIgnoreCase)).ToArray();
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, rows.Length == 0 ? table : rows));
    }

    private static async Task<ToolExecutionResult> ReferenceLookupAsync(
        IReferenceDataService? referenceDataService,
        string id,
        string input,
        CancellationToken cancellationToken,
        string language)
    {
        if (referenceDataService is null)
        {
            return Lookup(id, input);
        }

        var text = await referenceDataService.ReadTextAsync("data/reference/ymhut_reference_data.json", cancellationToken).ConfigureAwait(false);
        if (string.IsNullOrWhiteSpace(text))
        {
            return Lookup(id, input);
        }

        var sectionName = id switch
        {
            "mime_lookup" => "mime_types",
            "http_status_lookup" => "http_status",
            "port_lookup" => "ports",
            "dns_record_lookup" => "dns_records",
            "unicode_block_lookup" => "unicode_blocks",
            "charset_lookup" => "charsets",
            "timezone_abbr_lookup" => "timezone_abbreviations",
            "regex_preset_lookup" => "regex_presets",
            "magic_number_lookup" => "magic_numbers",
            _ => string.Empty
        };

        if (string.IsNullOrWhiteSpace(sectionName))
        {
            return Lookup(id, input);
        }

        try
        {
            using var document = JsonDocument.Parse(text);
            if (!document.RootElement.TryGetProperty(sectionName, out var section) || section.ValueKind != JsonValueKind.Array)
            {
                return Lookup(id, input);
            }

            var query = input.Trim();
            var rows = section.EnumerateArray()
                .Where(item => string.IsNullOrWhiteSpace(query) || ReferenceItemMatches(item, query))
                .Take(80)
                .Select(item => FormatReferenceItem(item, id, language))
                .ToList();
            if (rows.Count == 0)
            {
                rows = section.EnumerateArray().Take(80).Select(item => FormatReferenceItem(item, id, language)).ToList();
            }

            var sourceKey = sectionName switch
            {
                "timezone_abbreviations" => "timezones",
                "regex_presets" => "unicode",
                "magic_numbers" => "iana",
                _ => sectionName
            };
            var source = ReferenceSourceName(sourceKey, language);
            var header = T(language, $"数据源：YMhut Reference Data / {source}", $"Data source: YMhut Reference Data / {source}");
            if (id == "port_lookup")
            {
                rows.Add(T(language, "翻译入口：未知英文参考内容可使用 Edge 打开官方参考页并启用网页翻译。", "Translation entry: for unknown English references, open the official reference page in Edge and use webpage translation."));
            }
            return ToolExecutionResult.Success($"{header}{Environment.NewLine}{string.Join(Environment.NewLine, rows)}");
        }
        catch
        {
            return Lookup(id, input);
        }
    }

    private static bool ReferenceItemMatches(JsonElement item, string query)
    {
        foreach (var property in item.EnumerateObject())
        {
            if (property.Value.ValueKind == JsonValueKind.String &&
                property.Value.GetString()?.Contains(query, StringComparison.OrdinalIgnoreCase) == true)
            {
                return true;
            }

            if (property.Value.ValueKind == JsonValueKind.Array &&
                property.Value.EnumerateArray().Any(value => value.ValueKind == JsonValueKind.String &&
                    value.GetString()?.Contains(query, StringComparison.OrdinalIgnoreCase) == true))
            {
                return true;
            }
        }

        return false;
    }

    private static string FormatReferenceItem(JsonElement item, string id, string language)
    {
        var code = JsonString(item, "code");
        var label = JsonString(item, "label");
        var description = id == "port_lookup"
            ? LocalizedPortDescription(code, JsonString(item, "description"), language)
            : JsonString(item, "description");
        var value = JsonString(item, "value");
        var aliases = JsonArrayString(item, "aliases");

        var line = string.IsNullOrWhiteSpace(value)
            ? $"{code} - {label}: {description}".Trim(' ', '-', ':')
            : $"{code} - {label}: {value} ({description})".Trim();
        return string.IsNullOrWhiteSpace(aliases)
            ? line
            : $"{line} / {T(language, "别名", "aliases")}: {aliases}";
    }

    private static string ReferenceSourceName(string key, string language)
    {
        return key switch
        {
            "mime_types" or "http_status" or "ports" or "dns_records" or "iana" => "IANA",
            "unicode" => "Unicode Consortium",
            "timezones" => "IANA Time Zone Database",
            _ => T(language, "公开参考数据", "Public reference data")
        };
    }

    private static string LocalizedPortDescription(string code, string fallback, string language)
    {
        if (English(language))
        {
            return fallback;
        }

        return code switch
        {
            "20" => "FTP 文件传输数据通道",
            "21" => "FTP 文件传输控制通道",
            "22" => "SSH 安全远程登录，也常用于 SFTP",
            "25" => "SMTP 邮件传输服务",
            "53" => "DNS 域名解析服务",
            "67" => "DHCP 服务端地址分配",
            "68" => "DHCP 客户端地址分配",
            "80" => "HTTP 网页服务",
            "110" => "POP3 邮件收取服务",
            "123" => "NTP 网络时间同步",
            "143" => "IMAP 邮件访问服务",
            "161" => "SNMP 网络设备管理",
            "389" => "LDAP 目录访问服务",
            "443" => "HTTPS 加密网页服务",
            "465" => "SMTPS 加密邮件发送",
            "587" => "邮件提交服务，常配合 STARTTLS",
            "993" => "IMAPS 加密邮件访问",
            "995" => "POP3S 加密邮件收取",
            "1433" => "Microsoft SQL Server 数据库服务",
            "3306" => "MySQL 数据库服务",
            "3389" => "RDP 远程桌面协议",
            "5432" => "PostgreSQL 数据库服务",
            "6379" => "Redis 键值存储服务",
            "8080" => "常见 HTTP 备用端口或代理端口",
            _ => fallback
        };
    }

    private static string JsonString(JsonElement item, string name)
    {
        return item.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String
            ? value.GetString() ?? string.Empty
            : string.Empty;
    }

    private static string JsonArrayString(JsonElement item, string name)
    {
        return item.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.Array
            ? string.Join(", ", value.EnumerateArray().Where(element => element.ValueKind == JsonValueKind.String).Select(element => element.GetString()))
            : string.Empty;
    }

    private static ToolExecutionResult SystemInfo()
    {
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"OS: {RuntimeInformation.OSDescription}",
            $"Framework: {RuntimeInformation.FrameworkDescription}",
            $"Machine: {Environment.MachineName}",
            $"Processors: {Environment.ProcessorCount}",
            $"Memory: {Environment.WorkingSet / 1024 / 1024} MB"
        }));
    }

    private static ToolExecutionResult Benchmark()
    {
        var started = DateTime.UtcNow;
        var data = Enumerable.Range(1, 10000).Select(value => SHA256.HashData(BitConverter.GetBytes(value))).ToArray();
        var elapsed = DateTime.UtcNow - started;
        return ToolExecutionResult.Success($"Samples: {data.Length}{Environment.NewLine}Elapsed: {elapsed.TotalMilliseconds:0.00} ms");
    }

    private static ToolExecutionResult SystemTool(string input, string language)
    {
        var command = input.Trim().ToLowerInvariant();
        if (string.IsNullOrWhiteSpace(command) || command is "help" or "status")
        {
            return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
            {
                T(language, "系统工具", "System tools"),
                T(language, "可用命令：status、temp、path、env、process", "Available commands: status, temp, path, env, process"),
                T(language, "可打开：calculator、paint、rdp、control_panel、registry_editor、task_manager、services、device_manager、disk_management、event_viewer、windows_settings、explorer、notepad、cmd、powershell", "Launchable: calculator, paint, rdp, control_panel, registry_editor, task_manager, services, device_manager, disk_management, event_viewer, windows_settings, explorer, notepad, cmd, powershell"),
                string.Empty,
                $"OS: {RuntimeInformation.OSDescription}",
                $"Machine: {Environment.MachineName}",
                $"User: {Environment.UserName}",
                $"Processors: {Environment.ProcessorCount}",
                $".NET: {RuntimeInformation.FrameworkDescription}",
                $"Temp: {Path.GetTempPath()}"
            }));
        }

        if (TryResolveSystemLauncher(command, out var launcher))
        {
            try
            {
                var startInfo = new global::System.Diagnostics.ProcessStartInfo
                {
                    FileName = launcher.FileName,
                    Arguments = launcher.Arguments,
                    UseShellExecute = true
                };
                global::System.Diagnostics.Process.Start(startInfo);
                return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
                {
                    T(language, $"已打开：{launcher.DisplayName}", $"Opened: {launcher.DisplayName}"),
                    T(language, $"入口：{launcher.Command}", $"Entry: {launcher.Command}"),
                    T(language, "提示：系统工具由 Windows 直接启动，YMhut Box 不会读取或保存其窗口内容。", "Tip: Windows launches this system tool directly. YMhut Box does not read or store its window content.")
                }));
            }
            catch (Exception exception)
            {
                return ToolExecutionResult.Fail(T(language, $"无法打开 {launcher.DisplayName}：{exception.Message}", $"Could not open {launcher.DisplayName}: {exception.Message}"));
            }
        }

        if (command.StartsWith("temp", StringComparison.Ordinal))
        {
            return TempPreview();
        }

        if (command.StartsWith("path", StringComparison.Ordinal))
        {
            return ToolExecutionResult.Success(string.Join(Environment.NewLine, (Environment.GetEnvironmentVariable("PATH") ?? string.Empty)
                .Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
                .Distinct(StringComparer.OrdinalIgnoreCase)));
        }

        if (command.StartsWith("env", StringComparison.Ordinal))
        {
            var names = new[] { "OS", "PROCESSOR_ARCHITECTURE", "NUMBER_OF_PROCESSORS", "USERNAME", "USERPROFILE", "TEMP", "TMP" };
            return ToolExecutionResult.Success(string.Join(Environment.NewLine, names.Select(name => $"{name}={Environment.GetEnvironmentVariable(name)}")));
        }

        if (command.StartsWith("process", StringComparison.Ordinal))
        {
            var rows = global::System.Diagnostics.Process.GetProcesses()
                .OrderByDescending(process =>
                {
                    try { return process.WorkingSet64; }
                    catch { return 0; }
                })
                .Take(20)
                .Select(process =>
                {
                    try { return $"{process.ProcessName}\tPID {process.Id}\t{process.WorkingSet64 / 1024 / 1024:N0} MB"; }
                    catch { return $"{process.ProcessName}\tPID {process.Id}"; }
                });
            return ToolExecutionResult.Success(string.Join(Environment.NewLine, rows));
        }

        return ToolExecutionResult.Fail(T(language, "未知系统工具命令。可输入：status、temp、path、env、process，或选择 calculator、paint、rdp、control_panel、registry_editor 等 Windows 工具。", "Unknown system tool command. Enter status, temp, path, env, process, or choose Windows tools such as calculator, paint, rdp, control_panel, registry_editor."));
    }

    private static bool TryResolveSystemLauncher(string command, out SystemLauncher launcher)
    {
        var token = command.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).FirstOrDefault() ?? command;
        var normalized = token.Replace("-", "_", StringComparison.OrdinalIgnoreCase);
        launcher = normalized switch
        {
            "calc" or "calculator" => new("计算器", "calculator", "calc.exe"),
            "paint" or "mspaint" => new("画图", "paint", "mspaint.exe"),
            "rdp" or "mstsc" or "remote_desktop" => new("远程桌面连接", "rdp", "mstsc.exe"),
            "control" or "control_panel" => new("控制面板", "control_panel", "control.exe"),
            "regedit" or "registry" or "registry_editor" => new("注册表编辑器", "registry_editor", "regedit.exe"),
            "taskmgr" or "task_manager" => new("任务管理器", "task_manager", "taskmgr.exe"),
            "services" or "services_msc" => new("服务", "services", "services.msc"),
            "devmgmt" or "device_manager" => new("设备管理器", "device_manager", "devmgmt.msc"),
            "diskmgmt" or "disk_management" => new("磁盘管理", "disk_management", "diskmgmt.msc"),
            "eventvwr" or "event_viewer" => new("事件查看器", "event_viewer", "eventvwr.msc"),
            "firewall" or "windows_firewall" => new("高级安全 Windows Defender 防火墙", "firewall", "wf.msc"),
            "msconfig" or "system_configuration" => new("系统配置", "msconfig", "msconfig.exe"),
            "appwiz" or "programs" => new("程序和功能", "programs", "appwiz.cpl"),
            "network_connections" or "ncpa" => new("网络连接", "network_connections", "ncpa.cpl"),
            "system_properties" or "sysdm" => new("系统属性", "system_properties", "sysdm.cpl"),
            "settings" or "windows_settings" => new("Windows 设置", "windows_settings", "ms-settings:"),
            "explorer" or "file_explorer" => new("文件资源管理器", "explorer", "explorer.exe"),
            "notepad" => new("记事本", "notepad", "notepad.exe"),
            "cmd" or "command_prompt" => new("命令提示符", "cmd", "cmd.exe"),
            "powershell" or "pwsh" => new("PowerShell", "powershell", "powershell.exe"),
            _ => default
        };

        return launcher.FileName is not null;
    }

    private readonly record struct SystemLauncher(string DisplayName, string Command, string FileName, string Arguments = "");

    private static ToolExecutionResult TempPreview()
    {
        var temp = Path.GetTempPath();
        if (!Directory.Exists(temp))
        {
            return ToolExecutionResult.Fail("未找到系统临时目录。");
        }

        var cutoff = DateTime.UtcNow.AddDays(-1);
        long bytes = 0;
        var count = 0;
        foreach (var file in Directory.EnumerateFiles(temp, "*", SearchOption.TopDirectoryOnly))
        {
            try
            {
                var info = new FileInfo(file);
                if (info.LastWriteTimeUtc <= cutoff)
                {
                    bytes += info.Length;
                    count++;
                }
            }
            catch
            {
            }
        }

        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            $"Temp: {temp}",
            $"Files older than 24h: {count}",
            $"Estimated size: {bytes / 1024d / 1024d:0.##} MB",
            "当前版本只做清理预览，避免误删正在使用的临时文件。"
        }));
    }

    private static ToolExecutionResult NativeTool(IToolModule module)
    {
        return ToolExecutionResult.Success($"{module.Metadata.Name} 已迁移为 WinUI 3 原生页面，请从工具详情页直接打开。");
    }

    private static ToolExecutionResult AiTranslation(string input)
    {
        var text = input.Trim();
        if (string.IsNullOrWhiteSpace(text))
        {
            return ToolExecutionResult.Fail("请输入要翻译的文本。");
        }

        return ToolExecutionResult.Fail("AI 翻译入口已迁移到 WinUI 3，但 Provider 尚未配置；请在后续设置 OpenAI、Azure OpenAI 或自定义 endpoint 后启用。");
    }

    private static ToolExecutionResult ShelfLife(string input)
    {
        var text = input.Trim();
        if (string.IsNullOrWhiteSpace(text))
        {
            return ToolExecutionResult.Fail("请输入食品名称或关键词。");
        }

        var days = Regex.Match(text, @"(?<value>\d+)\s*(?<unit>天|日|day|days|d|周|week|weeks|w|月|months|month|m|年|year|years|y)", RegexOptions.IgnoreCase);
        var date = Regex.Match(text, @"\d{4}[-/]\d{1,2}[-/]\d{1,2}");
        if (days.Success && date.Success && DateOnly.TryParse(date.Value.Replace('/', '-'), out var productionDate))
        {
            var value = int.Parse(days.Groups["value"].Value, CultureInfo.InvariantCulture);
            var unit = days.Groups["unit"].Value.ToLowerInvariant();
            var expiry = unit switch
            {
                "周" or "week" or "weeks" or "w" => productionDate.AddDays(value * 7),
                "月" or "month" or "months" or "m" => productionDate.AddMonths(value),
                "年" or "year" or "years" or "y" => productionDate.AddYears(value),
                _ => productionDate.AddDays(value)
            };
            var today = DateOnly.FromDateTime(DateTime.Today);
            var remaining = expiry.DayNumber - today.DayNumber;
            var status = remaining < 0 ? $"已过期 {-remaining} 天" : remaining == 0 ? "今天到期" : $"剩余 {remaining} 天";
            return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
            {
                "保质期计算",
                $"生产日期：{productionDate:yyyy-MM-dd}",
                $"到期日期：{expiry:yyyy-MM-dd}",
                $"状态：{status}",
                "提示：食品保质期应以包装标识和实际储存条件为准。"
            }));
        }

        var table = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
        {
            ["milk"] = ["牛奶：冷藏鲜奶通常 3-7 天；UHT 常温奶未开封常见 6-12 个月。"],
            ["牛奶"] = ["牛奶：冷藏鲜奶通常 3-7 天；UHT 常温奶未开封常见 6-12 个月。"],
            ["egg"] = ["鸡蛋：冷藏通常 3-5 周，破壳或熟制后应尽快食用。"],
            ["鸡蛋"] = ["鸡蛋：冷藏通常 3-5 周，破壳或熟制后应尽快食用。"],
            ["rice"] = ["大米：阴凉干燥密封通常 6-12 个月。"],
            ["大米"] = ["大米：阴凉干燥密封通常 6-12 个月。"],
            ["noodles"] = ["挂面/方便面：未开封常见 6-24 个月，见包装标识。"],
            ["面"] = ["挂面/方便面：未开封常见 6-24 个月，见包装标识。"],
            ["bread"] = ["面包：常温通常 2-4 天，冷冻可明显延长。"],
            ["面包"] = ["面包：常温通常 2-4 天，冷冻可明显延长。"],
            ["meat"] = ["生鲜肉：冷藏通常 1-2 天；冷冻可按品类保存数月。"],
            ["肉"] = ["生鲜肉：冷藏通常 1-2 天；冷冻可按品类保存数月。"],
            ["fish"] = ["鱼类/海鲜：冷藏通常 1-2 天，建议尽快食用。"],
            ["鱼"] = ["鱼类/海鲜：冷藏通常 1-2 天，建议尽快食用。"]
        };

        var hits = table
            .Where(pair => text.Contains(pair.Key, StringComparison.OrdinalIgnoreCase))
            .SelectMany(pair => pair.Value.Select(value => $"{pair.Key}: {value}"))
            .Distinct()
            .ToArray();

        return hits.Length > 0
            ? ToolExecutionResult.Success($"参考范围（非替代包装标识）：{Environment.NewLine}{string.Join(Environment.NewLine, hits)}")
            : ToolExecutionResult.Success("未命中内置常见食品数据。可输入“2026-05-01 180天”直接计算到期日；实际保质期以包装标识和储存条件为准。");
    }

    private static async Task<ToolExecutionResult> QqProfileAsync(string input, CancellationToken cancellationToken, IApiManager? apiManager, string language)
    {
        var profile = await QqProfileProvider.QueryAsync(input, apiManager, cancellationToken, language).ConfigureAwait(false);
        var outputLines = new List<string>
        {
            $"QQ: {profile.Qq}",
            $"头像: {profile.AvatarUrl}",
            $"资料状态: {profile.StatusMessage}"
        };
        outputLines.AddRange(profile.Fields.Where(pair => pair.Key != "QQ").Select(pair => $"{pair.Key}: {pair.Value}"));
        var output = string.Join(Environment.NewLine, outputLines);
        var blocks = new List<ToolResultBlock>
        {
            ToolResultBlock.Metric(
                T(language, "QQ 摘要", "QQ summary"),
                [
                    new("QQ", profile.Qq),
                    new(T(language, "资料状态", "Profile status"), profile.ProfileAvailable ? T(language, "可用", "Available") : T(language, "部分可用", "Partially available"))
                ],
                new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                {
                    ["contentType"] = "metric",
                    ["displayMode"] = "summary"
                }),
            ToolResultBlock.Media(
                ToolResultBlockKind.Image,
                T(language, "QQ 头像", "QQ avatar"),
                profile.AvatarUrl,
                profile.AvatarUrl,
                new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                {
                    ["contentType"] = "image/png",
                    ["displayMode"] = "preview",
                    ["sourceVisibility"] = "publicTrusted"
                }),
            ToolResultBlock.KeyValue(
                T(language, "公开资料", "Public profile"),
                profile.Fields.Select(pair => new KeyValuePair<string, string>(pair.Key, pair.Value)).ToArray(),
                new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                {
                    ["contentType"] = "text/plain",
                    ["displayMode"] = "details",
                    ["sourceVisibility"] = profile.ProfileAvailable ? "publicTrusted" : "sensitivePrivate",
                    ["sourceSensitivity"] = profile.ProfileAvailable ? "public" : "partial"
                }),
            ToolResultBlock.List(
                ToolResultBlockKind.Status,
                T(language, "来源状态", "Source status"),
                [new(profile.ProfileAvailable ? "ok" : "info", profile.StatusMessage, profile.SourceName, profile.ProfileAvailable ? "ok" : "info", string.Empty)])
        };
        var document = new ToolResultDocument(
            "qq_avatar",
            ToolResultKind.ImagePreview,
            output,
            blocks,
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                ["toolId"] = "qq_avatar",
                ["resultKind"] = ToolResultKind.ImagePreview.ToString(),
                ["displayProfile"] = "profile-image",
                ["sourceVisibility"] = profile.ProfileAvailable ? "publicTrusted" : "sensitivePrivate",
                ["generatedAt"] = DateTimeOffset.Now.ToString("O")
            },
            output,
            profile.SourceName,
            "ok");
        return ToolExecutionResult.Success(output, document);
    }

    private static async Task<ToolExecutionResult> RandomCinemaAsync(string input, CancellationToken cancellationToken, IApiManager? apiManager, string language)
    {
        var manager = apiManager ?? ApiManager.CreateDefault();
        var response = await manager.FetchAsync("media_types", string.Empty, cancellationToken).ConfigureAwait(false);
        if (response.Success)
        {
            try
            {
                var catalog = RemoteMediaCatalogParser.Parse(response.Content, response.FetchedAt);
                var output = RemoteMediaCatalogFormatter.FormatRandomSource(catalog, input, language);
                return ToolExecutionResult.Success(output);
            }
            catch
            {
            }
        }

        return RandomCinemaFallback(input);
    }

    private static ToolExecutionResult RandomCinemaFallback(string input)
    {
        var sources = new[]
        {
            "https://picsum.photos/seed/ymhut-box/1280/720",
            "https://images.unsplash.com/photo-1497032628192-86f99bcd76bc?w=1280",
            "https://images.unsplash.com/photo-1489599849927-2ee91cede3ba?w=1280"
        };

        var index = Math.Abs((input ?? string.Empty).GetHashCode()) % sources.Length;
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            "RandomCinema source:",
            sources[index]
        }));
    }

    private static ToolExecutionResult SmartSearch(string input, string language)
    {
        var parts = input.Replace("\r\n", "\n").Split('\n', 2, StringSplitOptions.TrimEntries);
        var requestedChannel = parts.Length > 1 ? parts[0] : "baidu";
        var rawQuery = parts.Length > 1 ? parts[1] : input;
        var query = Uri.EscapeDataString(rawQuery.Trim());
        if (string.IsNullOrWhiteSpace(query))
        {
            return ToolExecutionResult.Fail(T(language, "请输入搜索关键词。", "Enter search keywords."));
        }

        var channels = SearchChannels();
        var channel = channels.FirstOrDefault(item => string.Equals(item.Id, requestedChannel, StringComparison.OrdinalIgnoreCase))
            ?? channels[0];
        var url = string.Format(CultureInfo.InvariantCulture, channel.UrlTemplate, query);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, new[]
        {
            T(language, $"搜索渠道：{channel.ZhName}", $"Search channel: {channel.EnName}"),
            T(language, $"关键词：{rawQuery.Trim()}", $"Keywords: {rawQuery.Trim()}"),
            $"{channel.EnName} - {url}"
        }));
    }

    private static IReadOnlyList<SearchChannel> SearchChannels() =>
    [
        new("baidu", "百度", "Baidu", "https://www.baidu.com/s?wd={0}"),
        new("bing", "Bing", "Bing", "https://www.bing.com/search?q={0}"),
        new("google", "Google", "Google", "https://www.google.com/search?q={0}"),
        new("duckduckgo", "DuckDuckGo", "DuckDuckGo", "https://duckduckgo.com/?q={0}"),
        new("sogou", "搜狗", "Sogou", "https://www.sogou.com/web?query={0}"),
        new("so360", "360 搜索", "360 Search", "https://www.so.com/s?q={0}"),
        new("zhihu", "知乎", "Zhihu", "https://www.zhihu.com/search?q={0}"),
        new("bilibili", "B 站", "Bilibili", "https://search.bilibili.com/all?keyword={0}"),
        new("weibo", "微博", "Weibo", "https://s.weibo.com/weibo?q={0}"),
        new("github", "GitHub", "GitHub", "https://github.com/search?q={0}"),
        new("stackoverflow", "Stack Overflow", "Stack Overflow", "https://stackoverflow.com/search?q={0}"),
        new("mdn", "MDN", "MDN", "https://developer.mozilla.org/search?q={0}")
    ];

    private sealed record SearchChannel(string Id, string ZhName, string EnName, string UrlTemplate);

    private static ToolExecutionResult UuToolSuite(string input)
    {
        var catalog = new ToolCatalog();
        var query = input.Trim();
        var matches = string.IsNullOrWhiteSpace(query)
            ? catalog.Modules
                .Where(module => module.Metadata.Category is ToolCategory.Text or ToolCategory.Calculator or ToolCategory.Dev or ToolCategory.Network)
                .Take(20)
            : catalog.Search(query).Take(20);

        var lines = new List<string>
        {
            "YMhut UU Tool Suite",
            "Aggregated native WinUI tools",
            string.Empty
        };

        foreach (var module in matches)
        {
            lines.Add($"{module.Id}\t{module.Metadata.Name}\t{module.Metadata.Description}");
        }

        if (lines.Count == 3)
        {
            lines.Add("No matching tools found.");
        }

        return ToolExecutionResult.Success(string.Join(Environment.NewLine, lines));
    }

    private static async Task<ToolExecutionResult> CarInfoAsync(
        IReferenceDataService? referenceDataService,
        string input,
        CancellationToken cancellationToken)
    {
        var query = input.Trim();
        var plateMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["京"] = "北京",
            ["津"] = "天津",
            ["沪"] = "上海",
            ["渝"] = "重庆",
            ["冀"] = "河北",
            ["豫"] = "河南",
            ["云"] = "云南",
            ["辽"] = "辽宁",
            ["黑"] = "黑龙江",
            ["湘"] = "湖南",
            ["皖"] = "安徽",
            ["鲁"] = "山东",
            ["新"] = "新疆",
            ["苏"] = "江苏",
            ["浙"] = "浙江",
            ["赣"] = "江西",
            ["鄂"] = "湖北",
            ["桂"] = "广西",
            ["甘"] = "甘肃",
            ["晋"] = "山西",
            ["蒙"] = "内蒙古",
            ["陕"] = "陕西",
            ["吉"] = "吉林",
            ["闽"] = "福建",
            ["贵"] = "贵州",
            ["粤"] = "广东",
            ["青"] = "青海",
            ["藏"] = "西藏",
            ["川"] = "四川",
            ["宁"] = "宁夏",
            ["琼"] = "海南"
        };

        var lines = new List<string>
        {
            "数据源：本地规则 + AMap 行政区划 adcode/citycode 数据",
            "说明：车牌省级简称为公开交通规则；城市编码来自随包资源，精确号段仍以公安交管平台为准。",
            string.Empty
        };

        var plateMatch = Regex.Match(query, @"[\u4e00-\u9fff][A-Z0-9]?[A-Z0-9]{4,6}", RegexOptions.IgnoreCase);
        if (plateMatch.Success)
        {
            var prefix = plateMatch.Value[..1];
            if (plateMap.TryGetValue(prefix, out var province))
            {
                lines.Add($"车牌：{plateMatch.Value.ToUpperInvariant()}");
                lines.Add($"省级简称：{prefix} / {province}");
                lines.Add(string.Empty);
            }
        }
        else if (query.Length == 1 && plateMap.TryGetValue(query, out var province))
        {
            lines.Add($"省级简称：{query} / {province}");
            lines.Add(string.Empty);
        }

        if (referenceDataService is not null)
        {
            var csv = await referenceDataService.ReadTextAsync("data/AMap_adcode_citycode.csv", cancellationToken).ConfigureAwait(false);
            if (!string.IsNullOrWhiteSpace(csv))
            {
                var matches = csv.Replace("\r\n", "\n")
                    .Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
                    .Skip(1)
                    .Select(line => line.Split(','))
                    .Where(parts => parts.Length >= 3)
                    .Where(parts => string.IsNullOrWhiteSpace(query) ||
                        parts[0].Contains(query, StringComparison.OrdinalIgnoreCase) ||
                        parts[1].Contains(query, StringComparison.OrdinalIgnoreCase) ||
                        parts[2].Contains(query, StringComparison.OrdinalIgnoreCase) ||
                        (plateMap.TryGetValue(query[..Math.Min(query.Length, 1)], out var mappedProvince) && parts[0].Contains(mappedProvince, StringComparison.OrdinalIgnoreCase)))
                    .Take(40)
                    .Select(parts => $"{parts[0]} / adcode {parts[1]} / citycode {(string.IsNullOrWhiteSpace(parts[2]) ? "-" : parts[2])}")
                    .ToList();

                if (matches.Count > 0)
                {
                    lines.Add("行政区划命中：");
                    lines.AddRange(matches);
                    return ToolExecutionResult.Success(string.Join(Environment.NewLine, lines));
                }
            }
        }

        lines.Add("常用省级简称：");
        lines.Add(string.Join("  ", plateMap.Select(pair => $"{pair.Key}-{pair.Value}")));
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, lines));
    }

    private static async Task<ToolExecutionResult> SanguoshaSkinAsync(
        IReferenceDataService? referenceDataService,
        string input,
        CancellationToken cancellationToken)
    {
        if (referenceDataService is null)
        {
            return ToolExecutionResult.Fail("参考数据服务不可用。");
        }

        var text = await referenceDataService.ReadTextAsync("data/sanguosha/skin_config.json", cancellationToken).ConfigureAwait(false);
        if (string.IsNullOrWhiteSpace(text))
        {
            return ToolExecutionResult.Fail("未找到三国杀皮肤配置：data/sanguosha/skin_config.json");
        }

        using var document = JsonDocument.Parse(text);
        if (document.RootElement.ValueKind != JsonValueKind.Array)
        {
            return ToolExecutionResult.Fail("三国杀皮肤配置格式无效。");
        }

        var query = input.Trim();
        var rows = document.RootElement.EnumerateArray()
            .Where(item => string.IsNullOrWhiteSpace(query) ||
                JsonString(item, "skinId").Contains(query, StringComparison.OrdinalIgnoreCase) ||
                JsonString(item, "itemName").Contains(query, StringComparison.OrdinalIgnoreCase) ||
                JsonString(item, "generalName").Contains(query, StringComparison.OrdinalIgnoreCase) ||
                JsonString(item, "skinName").Contains(query, StringComparison.OrdinalIgnoreCase))
            .Take(80)
            .Select(item =>
            {
                var skinId = JsonString(item, "skinId");
                var itemName = JsonString(item, "itemName");
                var generalName = JsonString(item, "generalName");
                var skinName = JsonString(item, "skinName");
                var gender = JsonString(item, "gender");
                return $"{skinId} / {itemName} / 武将：{generalName} / 皮肤：{skinName} / {gender}";
            })
            .ToList();

        if (rows.Count == 0)
        {
            rows.Add("未命中皮肤，可输入武将名、皮肤名或 skinId。");
        }

        var header = string.IsNullOrWhiteSpace(query)
            ? "数据源：assets/data/sanguosha/skin_config.json（本地随包数据，显示前 80 条）"
            : $"数据源：assets/data/sanguosha/skin_config.json / 查询：{query}";
        return ToolExecutionResult.Success($"{header}{Environment.NewLine}{string.Join(Environment.NewLine, rows)}");
    }

    private static async Task<ToolExecutionResult> ReferenceSearchAsync(
        IReferenceDataService? referenceDataService,
        string relativePath,
        string input,
        CancellationToken cancellationToken)
    {
        if (referenceDataService is null)
        {
            return ToolExecutionResult.Fail("参考数据服务不可用。");
        }

        var text = await referenceDataService.ReadTextAsync(relativePath, cancellationToken).ConfigureAwait(false);
        if (string.IsNullOrWhiteSpace(text))
        {
            return ToolExecutionResult.Fail($"未找到参考数据：{relativePath}");
        }

        var lines = text.Replace("\r\n", "\n").Split('\n');
        var query = input.Trim();
        var matches = string.IsNullOrWhiteSpace(query)
            ? lines.Take(40)
            : lines.Where(line => line.Contains(query, StringComparison.OrdinalIgnoreCase)).Take(40);
        return ToolExecutionResult.Success(string.Join(Environment.NewLine, matches));
    }

    private static async Task<ToolExecutionResult> WeatherAsync(string input, CancellationToken cancellationToken, IApiManager? apiManager, string language)
    {
        var manager = apiManager ?? ApiManager.CreateDefault();
        var cityCode = ChinaWeatherCityCode(input);
        if (!string.IsNullOrWhiteSpace(cityCode))
        {
            var chinaUri = new Uri($"https://www.weather.com.cn/data/cityinfo/{cityCode}.html");
            var chinaWeather = await manager.FetchUriAsync("weather_china", chinaUri, input, cancellationToken).ConfigureAwait(false);
            if (chinaWeather.Success && !string.IsNullOrWhiteSpace(chinaWeather.Content))
            {
                return ToolExecutionResult.Success(FormatChinaWeather(chinaWeather.Content, language));
            }
        }

        var geocode = await manager.FetchAsync("weather", input, cancellationToken).ConfigureAwait(false);
        if (!geocode.Success)
        {
            return ToolExecutionResult.Fail(geocode.Error ?? T(language, "天气位置解析失败。", "Weather location lookup failed."));
        }

        if (!ApiEndpoints.TryGet("weather", out var endpoint))
        {
            return ToolExecutionResult.Fail(T(language, "天气数据源未配置。", "Weather source is not configured."));
        }

        using var document = JsonDocument.Parse(geocode.Content);
        if (!document.RootElement.TryGetProperty("results", out var results) ||
            results.ValueKind != JsonValueKind.Array ||
            results.GetArrayLength() == 0)
        {
            return ToolExecutionResult.Fail(T(language, "未找到匹配城市。请尝试输入城市中文名、英文名或拼音。", "No matching city found. Try a Chinese name, English name, or pinyin."));
        }

        var location = results[0];
        var latitude = location.GetProperty("latitude").GetDouble().ToString(CultureInfo.InvariantCulture);
        var longitude = location.GetProperty("longitude").GetDouble().ToString(CultureInfo.InvariantCulture);
        var uri = new Uri($"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum&timezone=auto&forecast_days=3");
        var forecast = await manager.FetchUriAsync("weather_forecast", uri, input, cancellationToken).ConfigureAwait(false);
        if (!forecast.Success)
        {
            return ToolExecutionResult.Fail(forecast.Error ?? T(language, "天气预报获取失败。", "Weather forecast request failed."));
        }

        var output = RemoteToolFormatter.FormatWeather(endpoint, geocode, forecast, language);
        return ToolExecutionResult.Success(output, ToolResultBuilder.FromRemote(endpoint, forecast, output, language));
    }

    private static string ChinaWeatherCityCode(string input)
    {
        var city = string.IsNullOrWhiteSpace(input) ? "北京" : input.Trim();
        var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["北京"] = "101010100", ["beijing"] = "101010100",
            ["上海"] = "101020100", ["shanghai"] = "101020100",
            ["广州"] = "101280101", ["guangzhou"] = "101280101",
            ["深圳"] = "101280601", ["shenzhen"] = "101280601",
            ["杭州"] = "101210101", ["hangzhou"] = "101210101",
            ["南京"] = "101190101", ["nanjing"] = "101190101",
            ["成都"] = "101270101", ["chengdu"] = "101270101",
            ["重庆"] = "101040100", ["chongqing"] = "101040100",
            ["武汉"] = "101200101", ["wuhan"] = "101200101",
            ["西安"] = "101110101", ["xian"] = "101110101", ["xi'an"] = "101110101",
            ["天津"] = "101030100", ["tianjin"] = "101030100",
            ["苏州"] = "101190401", ["suzhou"] = "101190401",
            ["长沙"] = "101250101", ["changsha"] = "101250101",
            ["郑州"] = "101180101", ["zhengzhou"] = "101180101",
            ["青岛"] = "101120201", ["qingdao"] = "101120201",
            ["厦门"] = "101230201", ["xiamen"] = "101230201"
        };
        return map.TryGetValue(city, out var code) ? code : string.Empty;
    }

    private static string FormatChinaWeather(string content, string language)
    {
        using var document = JsonDocument.Parse(content);
        var info = document.RootElement.TryGetProperty("weatherinfo", out var weatherInfo)
            ? weatherInfo
            : document.RootElement;
        var rows = new[]
        {
            T(language, "数据源：中国天气", "Data source: China Weather"),
            T(language, $"城市：{JsonString(info, "city")}", $"City: {JsonString(info, "city")}"),
            T(language, $"天气：{JsonString(info, "weather")}", $"Weather: {JsonString(info, "weather")}"),
            T(language, $"温度：{JsonString(info, "temp1")} / {JsonString(info, "temp2")}", $"Temperature: {JsonString(info, "temp1")} / {JsonString(info, "temp2")}"),
            T(language, $"发布时间：{JsonString(info, "ptime")}", $"Published: {JsonString(info, "ptime")}")
        };
        return string.Join(Environment.NewLine, rows.Where(row => !row.EndsWith("：", StringComparison.Ordinal) && !row.EndsWith(": ", StringComparison.Ordinal)));
    }


    private static async Task<ToolExecutionResult> TrainQueryAsync(string input, CancellationToken cancellationToken, IApiManager? apiManager, string language)
    {
        var manager = apiManager ?? ApiManager.CreateDefault();
        var stationResponse = await manager.FetchAsync("train_query", string.Empty, cancellationToken).ConfigureAwait(false);
        if (!stationResponse.Success)
        {
            return ToolExecutionResult.Fail(stationResponse.Error ?? "12306 车站数据获取失败。");
        }

        if (!ApiEndpoints.TryGet("train_query", out var endpoint))
        {
            return ToolExecutionResult.Fail("铁路数据源未配置。");
        }

        var query = ParseTrainQuery(input);
        if (query is null)
        {
            return RemoteToolFormatter.Format(endpoint, stationResponse, input, language);
        }

        var stations = RemoteToolFormatter.ParseStations(stationResponse.Content);
        var from = FindStation(stations, query.Value.From);
        var to = FindStation(stations, query.Value.To);
        if (from is null || to is null)
        {
            return RemoteToolFormatter.Format(endpoint, stationResponse, $"{query.Value.From} {query.Value.To}", language);
        }

        await manager.FetchUriAsync("train_init", new Uri("https://kyfw.12306.cn/otn/leftTicket/init"), input, cancellationToken).ConfigureAwait(false);
        var uri = new Uri($"https://kyfw.12306.cn/otn/leftTicket/queryG?leftTicketDTO.train_date={query.Value.Date:yyyy-MM-dd}&leftTicketDTO.from_station={from.Code}&leftTicketDTO.to_station={to.Code}&purpose_codes=ADULT");
        var ticketResponse = await manager.FetchUriAsync("train_left_ticket", uri, input, cancellationToken).ConfigureAwait(false);
        if (!ticketResponse.Success)
        {
            return ToolExecutionResult.Fail(ticketResponse.Error ?? "12306 余票查询失败。");
        }

        var output = RemoteToolFormatter.FormatTrainLeftTickets(
            endpoint,
            stationResponse,
            ticketResponse,
            from.Name,
            to.Name,
            query.Value.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
        return ToolExecutionResult.Success(output, ToolResultBuilder.FromRemote(endpoint, ticketResponse, output, language));
    }

    private static async Task<ToolExecutionResult> AiLatestNewsAsync(string input, CancellationToken cancellationToken, IApiManager? apiManager, string language)
    {
        if (string.IsNullOrWhiteSpace(input) || input.Trim().Equals("key=", StringComparison.OrdinalIgnoreCase))
        {
            return ToolExecutionResult.Success(T(language,
                "状态: 需要输入接口 key 后再查询。\n说明: AI 全网最新资讯不会在缺少 key 时自动请求远程服务。",
                "Status: enter an API key before querying.\nNote: AI latest news will not request the remote service without a key."));
        }

        return await ExecuteKnownRemoteOrDescribeAsync(new ToolModule(new ToolMetadata(
            "ai_latest_news",
            "AI 全网最新资讯",
            "按分类查看 AI 新闻",
            ToolCategory.Data,
            ["ai", "news"],
            false,
            "\uE9D9")),
            input,
            cancellationToken,
            apiManager,
            language).ConfigureAwait(false);
    }

    private static async Task<ToolExecutionResult> CityRouteQueryAsync(string input, CancellationToken cancellationToken, IApiManager? apiManager, string language)
    {
        if (!HasCityRoutePair(input))
        {
            return ToolExecutionResult.Success(T(language,
                "状态: 请输入两个城市后再查询。\n示例: 广州 深圳\n示例: 广州 -> 深圳",
                "Status: enter two cities before querying.\nExample: Guangzhou Shenzhen\nExample: Guangzhou -> Shenzhen"));
        }

        return await ExecuteKnownRemoteOrDescribeAsync(new ToolModule(new ToolMetadata(
            "city_route_query",
            "城际路线查询",
            "查询两个城市之间的路线成本",
            ToolCategory.Data,
            ["city", "route"],
            false,
            "\uE9D9")),
            input,
            cancellationToken,
            apiManager,
            language).ConfigureAwait(false);
    }

    private static bool HasCityRoutePair(string input)
    {
        var value = input.Replace("\r\n", "\n").Trim();
        if (string.IsNullOrWhiteSpace(value))
        {
            return false;
        }

        if (value.Contains("->", StringComparison.Ordinal))
        {
            var parts = value.Split("->", 2, StringSplitOptions.TrimEntries);
            return parts.All(part => !string.IsNullOrWhiteSpace(part));
        }

        var lines = value.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (lines.Length >= 2)
        {
            return true;
        }

        return SplitTokens(value).Take(2).Count() >= 2;
    }

    private static async Task<ToolExecutionResult> ExecuteKnownRemoteOrDescribeAsync(IToolModule module, string input, CancellationToken cancellationToken, IApiManager? apiManager, string language)
    {
        if (ApiEndpoints.TryResolve(module.Id, input, out var endpoint, out var uri))
        {
            var manager = apiManager ?? ApiManager.CreateDefault();
            var response = await manager.FetchAsync(endpoint.Id, input, cancellationToken).ConfigureAwait(false);
            if (response.Success)
            {
                return RemoteToolFormatter.Format(endpoint, response, input, language);
            }

            return RemoteToolFormatter.FormatFailure(endpoint, response, input, language);
        }

        return ToolExecutionResult.Success(
            T(language, $"工具：{module.Metadata.Name}", $"Tool: {module.Metadata.Name}") + Environment.NewLine +
            T(language, "状态：已迁移到 WinUI 3 原生工具入口。", "Status: migrated to the native WinUI 3 tool entry.") + Environment.NewLine +
            T(language, $"说明：此工具的元数据、搜索、最近使用和页面承载均已迁移为本地实现。输入内容如下：{Environment.NewLine}{input}", $"Note: metadata, search, recent usage, and page hosting are now local. Input:{Environment.NewLine}{input}"));
    }

    private static (DateOnly Date, string From, string To)? ParseTrainQuery(string input)
    {
        var tokens = SplitTokens(input).ToArray();
        if (tokens.Length < 2)
        {
            return null;
        }

        var date = DateOnly.FromDateTime(DateTime.Today.AddDays(1));
        var fromIndex = 0;
        if (DateOnly.TryParse(tokens[0], out var firstDate))
        {
            date = firstDate;
            fromIndex = 1;
        }
        else if (DateOnly.TryParse(tokens[^1], out var lastDate))
        {
            date = lastDate;
        }

        if (fromIndex + 1 >= tokens.Length)
        {
            return null;
        }

        return (date, tokens[fromIndex], tokens[fromIndex + 1]);
    }

    private static TrainStation? FindStation(IEnumerable<TrainStation> stations, string value)
    {
        return stations.FirstOrDefault(station =>
            station.Name.Equals(value, StringComparison.OrdinalIgnoreCase) ||
            station.Code.Equals(value, StringComparison.OrdinalIgnoreCase) ||
            station.Pinyin.Equals(value, StringComparison.OrdinalIgnoreCase)) ??
            stations.FirstOrDefault(station =>
                station.Name.Contains(value, StringComparison.OrdinalIgnoreCase) ||
                station.Pinyin.Contains(value, StringComparison.OrdinalIgnoreCase));
    }

    private static string TranslateChars(string input, IReadOnlyDictionary<char, char> map)
    {
        return string.Concat(input.Select(ch => map.TryGetValue(ch, out var replacement) ? replacement : ch));
    }

    private static string ConvertChinese(string input, bool toTraditional)
    {
        try
        {
            if (OperatingSystem.IsWindows())
            {
                return Strings.StrConv(input, toTraditional ? VbStrConv.TraditionalChinese : VbStrConv.SimplifiedChinese, 0x0804) ?? input;
            }
        }
        catch
        {
        }

        return TranslateChars(input, toTraditional ? SimplifiedToTraditional : TraditionalToSimplified);
    }

    private static IReadOnlyDictionary<char, char> BuildChineseMap()
    {
        return new Dictionary<char, char>
        {
            ['汉'] = '漢',
            ['马'] = '馬',
            ['门'] = '門',
            ['风'] = '風',
            ['云'] = '雲',
            ['车'] = '車',
            ['电'] = '電',
            ['国'] = '國',
            ['学'] = '學',
            ['术'] = '術',
            ['简'] = '簡',
            ['广'] = '廣',
            ['东'] = '東',
            ['网'] = '網',
            ['页'] = '頁',
            ['后'] = '後',
            ['发'] = '發',
            ['台'] = '臺',
            ['万'] = '萬',
            ['与'] = '與',
            ['乐'] = '樂',
            ['书'] = '書',
            ['买'] = '買',
            ['卖'] = '賣',
            ['亲'] = '親',
            ['会'] = '會',
            ['传'] = '傳',
            ['优'] = '優',
            ['写'] = '寫',
            ['军'] = '軍',
            ['农'] = '農',
            ['凤'] = '鳳',
            ['动'] = '動',
            ['区'] = '區',
            ['医'] = '醫',
            ['华'] = '華',
            ['单'] = '單',
            ['卫'] = '衛',
            ['历'] = '歷',
            ['县'] = '縣',
            ['双'] = '雙',
            ['听'] = '聽',
            ['问'] = '問',
            ['团'] = '團',
            ['园'] = '園',
            ['圆'] = '圓',
            ['场'] = '場',
            ['头'] = '頭',
            ['实'] = '實',
            ['对'] = '對',
            ['导'] = '導',
            ['将'] = '將',
            ['层'] = '層',
            ['岁'] = '歲',
            ['岛'] = '島',
            ['师'] = '師',
            ['开'] = '開',
            ['张'] = '張',
            ['强'] = '強',
            ['归'] = '歸',
            ['当'] = '當',
            ['录'] = '錄',
            ['忆'] = '憶',
            ['总'] = '總',
            ['护'] = '護',
            ['报'] = '報',
            ['损'] = '損',
            ['换'] = '換',
            ['摄'] = '攝',
            ['数'] = '數',
            ['无'] = '無',
            ['时'] = '時',
            ['机'] = '機',
            ['权'] = '權',
            ['条'] = '條',
            ['来'] = '來',
            ['构'] = '構',
            ['标'] = '標',
            ['样'] = '樣',
            ['档'] = '檔',
            ['桥'] = '橋',
            ['检'] = '檢',
            ['楼'] = '樓',
            ['气'] = '氣',
            ['汇'] = '匯',
            ['测'] = '測',
            ['济'] = '濟',
            ['湾'] = '灣',
            ['点'] = '點',
            ['热'] = '熱',
            ['爱'] = '愛',
            ['现'] = '現',
            ['画'] = '畫',
            ['监'] = '監',
            ['种'] = '種',
            ['积'] = '積',
            ['称'] = '稱',
            ['签'] = '簽',
            ['线'] = '線',
            ['组'] = '組',
            ['经'] = '經',
            ['结'] = '結',
            ['给'] = '給',
            ['统'] = '統',
            ['维'] = '維',
            ['职'] = '職',
            ['联'] = '聯',
            ['肤'] = '膚',
            ['脑'] = '腦',
            ['艺'] = '藝',
            ['节'] = '節',
            ['获'] = '獲',
            ['蓝'] = '藍',
            ['见'] = '見',
            ['观'] = '觀',
            ['视'] = '視',
            ['订'] = '訂',
            ['认'] = '認',
            ['计'] = '計',
            ['讯'] = '訊',
            ['记'] = '記',
            ['讲'] = '講',
            ['证'] = '證',
            ['评'] = '評',
            ['识'] = '識',
            ['译'] = '譯',
            ['词'] = '詞',
            ['试'] = '試',
            ['话'] = '話',
            ['该'] = '該',
            ['语'] = '語',
            ['说'] = '說',
            ['请'] = '請',
            ['读'] = '讀',
            ['调'] = '調',
            ['谢'] = '謝',
            ['账'] = '賬',
            ['货'] = '貨',
            ['费'] = '費',
            ['资'] = '資',
            ['购'] = '購',
            ['责'] = '責',
            ['贵'] = '貴',
            ['赛'] = '賽',
            ['赞'] = '讚',
            ['赠'] = '贈',
            ['轮'] = '輪',
            ['软'] = '軟',
            ['轻'] = '輕',
            ['输'] = '輸',
            ['边'] = '邊',
            ['远'] = '遠',
            ['连'] = '連',
            ['迟'] = '遲',
            ['选'] = '選',
            ['邮'] = '郵',
            ['里'] = '裏',
            ['针'] = '針',
            ['钟'] = '鐘',
            ['钢'] = '鋼',
            ['钱'] = '錢',
            ['铁'] = '鐵',
            ['铜'] = '銅',
            ['银'] = '銀',
            ['链'] = '鏈',
            ['锁'] = '鎖',
            ['镜'] = '鏡',
            ['长'] = '長',
            ['闭'] = '閉',
            ['间'] = '間',
            ['队'] = '隊',
            ['际'] = '際',
            ['阳'] = '陽',
            ['难'] = '難',
            ['预'] = '預',
            ['领'] = '領',
            ['题'] = '題',
            ['飞'] = '飛',
            ['饭'] = '飯',
            ['饮'] = '飲',
            ['馆'] = '館',
            ['驱'] = '驅',
            ['驶'] = '駛',
            ['验'] = '驗',
            ['骑'] = '騎',
            ['鱼'] = '魚',
            ['鸟'] = '鳥',
            ['鸣'] = '鳴',
            ['鸡'] = '雞',
            ['麦'] = '麥',
            ['齐'] = '齊',
            ['龄'] = '齡',
            ['龙'] = '龍'
        };
    }

    private static bool TryParseColor(string input, out byte r, out byte g, out byte b)
    {
        r = g = b = 0;
        var text = input.Trim();
        if (text.StartsWith('#'))
        {
            text = text[1..];
            if (text.Length == 3)
            {
                r = Convert.ToByte(new string(text[0], 2), 16);
                g = Convert.ToByte(new string(text[1], 2), 16);
                b = Convert.ToByte(new string(text[2], 2), 16);
                return true;
            }

            return text.Length == 6 &&
                byte.TryParse(text[..2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out r) &&
                byte.TryParse(text[2..4], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out g) &&
                byte.TryParse(text[4..6], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out b);
        }

        var parts = Regex.Split(text, @"[\s,]+").Where(part => part.Length > 0).ToArray();
        return parts.Length >= 3 &&
            byte.TryParse(parts[0], out r) &&
            byte.TryParse(parts[1], out g) &&
            byte.TryParse(parts[2], out b);
    }

    private static (double H, double S, double L) RgbToHsl(byte r, byte g, byte b)
    {
        var red = r / 255d;
        var green = g / 255d;
        var blue = b / 255d;
        var max = Math.Max(red, Math.Max(green, blue));
        var min = Math.Min(red, Math.Min(green, blue));
        var l = (max + min) / 2;
        if (Math.Abs(max - min) < 0.0001)
        {
            return (0, 0, l * 100);
        }

        var d = max - min;
        var s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        var h = max switch
        {
            var value when Math.Abs(value - red) < 0.0001 => ((green - blue) / d + (green < blue ? 6 : 0)) / 6,
            var value when Math.Abs(value - green) < 0.0001 => ((blue - red) / d + 2) / 6,
            _ => ((red - green) / d + 4) / 6
        };
        return (h * 360, s * 100, l * 100);
    }

    [SupportedOSPlatform("windows6.1")]
    private static RGBLuminanceSource CreateLuminanceSource(Bitmap bitmap)
    {
        using var clone = bitmap.Clone(
            new Rectangle(0, 0, bitmap.Width, bitmap.Height),
            PixelFormat.Format32bppArgb);
        var data = clone.LockBits(
            new Rectangle(0, 0, clone.Width, clone.Height),
            ImageLockMode.ReadOnly,
            PixelFormat.Format32bppArgb);
        try
        {
            var length = Math.Abs(data.Stride) * data.Height;
            var bytes = new byte[length];
            Marshal.Copy(data.Scan0, bytes, 0, length);
            return new RGBLuminanceSource(bytes, clone.Width, clone.Height, RGBLuminanceSource.BitmapFormat.BGRA32);
        }
        finally
        {
            clone.UnlockBits(data);
        }
    }

    private static string NextAvailablePath(string path)
    {
        if (!File.Exists(path))
        {
            return path;
        }

        var directory = Path.GetDirectoryName(path) ?? Environment.CurrentDirectory;
        var name = Path.GetFileNameWithoutExtension(path);
        var extension = Path.GetExtension(path);
        for (var index = 1; index < 1000; index++)
        {
            var candidate = Path.Combine(directory, $"{name}-{index}{extension}");
            if (!File.Exists(candidate))
            {
                return candidate;
            }
        }

        return Path.Combine(directory, $"{name}-{Guid.NewGuid():N}{extension}");
    }

    private static string HashFile(string path)
    {
        using var stream = File.OpenRead(path);
        return Hex(SHA256.HashData(stream));
    }

    private static bool LooksLikePath(string value)
    {
        return value.Contains(Path.DirectorySeparatorChar) ||
            value.Contains(Path.AltDirectorySeparatorChar) ||
            value.Contains(':') ||
            Path.HasExtension(value);
    }

    private static string SanitizeFileName(string value)
    {
        var invalid = Path.GetInvalidFileNameChars().ToHashSet();
        var sanitized = new string(value.Select(ch => invalid.Contains(ch) ? '_' : ch).ToArray()).Trim();
        return string.IsNullOrWhiteSpace(sanitized) ? "untitled" : sanitized;
    }

    private static string EnsureUniqueFileName(string value, ISet<string> used)
    {
        var candidate = value;
        var name = Path.GetFileNameWithoutExtension(value);
        var extension = Path.GetExtension(value);
        var index = 1;
        while (!used.Add(candidate))
        {
            candidate = $"{name}-{index++}{extension}";
        }

        return candidate;
    }

    private static string Hex(byte[] bytes) => Convert.ToHexString(bytes).ToLowerInvariant();

    private static long Factorial(int value)
    {
        return Enumerable.Range(1, Math.Max(1, value)).Aggregate(1L, (acc, next) => acc * next);
    }

    private static long Gcd(long left, long right)
    {
        while (right != 0)
        {
            (left, right) = (right, left % right);
        }
        return Math.Abs(left);
    }

    private static string ToIPv4(uint value)
    {
        var bytes = BitConverter.GetBytes(value);
        if (BitConverter.IsLittleEndian)
        {
            Array.Reverse(bytes);
        }
        return new IPAddress(bytes).ToString();
    }

    private static string PeriodBetween(DateOnly start, DateOnly end)
    {
        var years = end.Year - start.Year;
        var months = end.Month - start.Month;
        var days = end.Day - start.Day;
        if (days < 0)
        {
            months--;
            days += DateTime.DaysInMonth(end.AddMonths(-1).Year, end.AddMonths(-1).Month);
        }
        if (months < 0)
        {
            years--;
            months += 12;
        }
        return $"{years} years {months} months {days} days";
    }

    private static int CountWorkdays(DateOnly start, DateOnly end)
    {
        var count = 0;
        for (var date = start; date <= end; date = date.AddDays(1))
        {
            if (date.DayOfWeek is not DayOfWeek.Saturday and not DayOfWeek.Sunday)
            {
                count++;
            }
        }

        return count;
    }

    private static IEnumerable<string> Lines(string input)
    {
        return input.Replace("\r\n", "\n").Split('\n').Select(line => line.Trim()).Where(line => line.Length > 0);
    }

    private static IEnumerable<string> SplitTokens(string input)
    {
        return Regex.Split(input.Trim(), @"[\s,;|\t]+").Where(token => token.Length > 0);
    }

    private static IEnumerable<string> SplitCsv(string input)
    {
        return input.Split(',').Select(cell => cell.Trim().Trim('"'));
    }

    private static IEnumerable<double> Numbers(string input)
    {
        return Regex.Matches(input, @"-?\d+(?:\.\d+)?")
            .Select(match => double.Parse(match.Value, CultureInfo.InvariantCulture));
    }

    private static double NumberAt(IReadOnlyList<double> values, int index, double fallback)
    {
        return index >= 0 && index < values.Count ? values[index] : fallback;
    }

    private static string DecodeBase64Url(string value)
    {
        var padded = value.Replace('-', '+').Replace('_', '/');
        padded = padded.PadRight(padded.Length + (4 - padded.Length % 4) % 4, '=');
        return Encoding.UTF8.GetString(Convert.FromBase64String(padded));
    }
}
