#include "agentty/tool/spec.hpp"
#include "agentty/tool/tools.hpp"
#include "agentty/tool/util/arg_reader.hpp"
#include "agentty/tool/util/fs_helpers.hpp"
#include "agentty/tool/util/glob.hpp"
#include "agentty/tool/util/tool_args.hpp"
#include "agentty/domain/refined.hpp"

#include <filesystem>
#include <format>
#include <sstream>
#include <string>
#include <system_error>

#include <nlohmann/json.hpp>

namespace agentty::tools {

using json = nlohmann::json;
namespace fs = std::filesystem;

namespace {

struct GlobArgs {
    // pattern: non-blank by construction. A whitespace-only or empty
    // glob would either match nothing or match everything depending on
    // the substring fallback — either way useless.
    domain::NonBlank<std::string> pattern;
    std::string                   root;
    std::string                   display_description;
};

std::expected<GlobArgs, ToolError> parse_glob_args(const json& j) {
    util::ArgReader ar(j);
    auto pat_opt = ar.require_str("pattern");
    if (!pat_opt)
        return std::unexpected(ToolError::invalid_args("pattern required"));
    auto refined_pat = domain::NonBlank<std::string>::try_make(*std::move(pat_opt));
    if (!refined_pat)
        return std::unexpected(ToolError::invalid_args(std::format(
            "pattern {} (received only whitespace)",
            refined_pat.error().what)));
    return GlobArgs{
        *std::move(refined_pat),
        ar.str("path", "."),
        ar.str("display_description", ""),
    };
}

ExecResult run_glob(const GlobArgs& a) {
    // Workspace boundary check — even with default ".", canonicalising
    // it ensures glob can't be tricked into walking up via "../.."
    // tricks the model might try.
    auto wp = util::make_workspace_path_checked(a.root, "glob");
    if (!wp) return std::unexpected(std::move(wp.error()));

    // If the pattern has no glob metacharacters, fall back to substring
    // matching. The model often types `foo.cpp` intending "find anything
    // named that"; forcing it to write `*foo.cpp*` would be annoying.
    const auto& pat = a.pattern.value();
    bool has_glob = pat.find_first_of("*?[") != std::string::npos;

    std::ostringstream out;
    int n = 0;
    std::error_code ec;
    for (auto it = fs::recursive_directory_iterator(wp->path(),
                fs::directory_options::skip_permission_denied, ec);
         it != fs::recursive_directory_iterator(); it.increment(ec)) {
        if (ec) { ec.clear(); continue; }
        auto fn = it->path().filename().string();
        if (it->is_directory(ec)) {
            if (util::should_skip_dir(fn)) { it.disable_recursion_pending(); continue; }
            continue;
        }
        if (!it->is_regular_file(ec)) continue;
        bool hit = has_glob ? util::glob_match(pat, fn)
                            : fn.find(pat) != std::string::npos;
        if (hit) {
            out << it->path().string() << "\n";
            if (++n > 500) { out << "[>500, truncated]\n"; break; }
        }
    }
    if (n == 0)
        return ToolOutput{"no matches. Try a different pattern, or `list_dir` "
                          "on parent directories to see what exists.",
                          std::nullopt};
    std::string body = "Found " + std::to_string(n) + " file(s):\n" + out.str();
    if (!a.display_description.empty())
        body = a.display_description + "\n" + body;
    return ToolOutput{std::move(body), std::nullopt};
}

} // namespace

ToolDef tool_glob() {
    ToolDef t;
    constexpr const auto& kSpec = spec::require<"glob">();
    t.name = ToolName{std::string{kSpec.name}};
    t.description = "Find files by glob pattern. Supports `*` (any run), `?` (one char), "
                    "`[abc]` classes, and bare substrings. Matches against filename "
                    "(not full path). Case-insensitive on Windows.";
    t.input_schema = json{
        {"type","object"},
        {"required", {"pattern"}},
        {"properties", {
            {"display_description", {{"type","string"},
                {"description","One-line summary shown in the UI. Optional."}}},
            {"pattern", {{"type","string"}, {"description","Glob pattern, e.g. *.cpp"}}},
            {"path",    {{"type","string"}, {"description","Root directory (default: cwd)"}}},
        }},
    };
    t.effects = kSpec.effects;
    t.eager_input_streaming = kSpec.eager_input_streaming;
    t.execute = util::adapt<GlobArgs>(parse_glob_args, run_glob);
    return t;
}

} // namespace agentty::tools
