using System.IO;
using System.Text;
using System.Text.Json;

namespace AxCopilot.Services.Agent;

/// <summary>
/// 작업 폴더의 디렉토리 트리 구조를 생성하는 도구.
/// LLM이 프로젝트 전체 구조를 파악하고 적절한 파일을 찾을 수 있도록 돕습니다.
/// </summary>
public class FolderMapTool : IAgentTool
{
    public string Name => "folder_map";
    public string Description =>
        "Generate a directory tree map of the work folder or a specified subfolder. " +
        "Shows folders and files in a tree structure. Use this to understand the project layout before reading or editing files.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["path"] = new() { Type = "string", Description = "Subdirectory to map. Optional, defaults to work folder root." },
            ["depth"] = new() { Type = "integer", Description = "Maximum depth to traverse (1-10). Default: 3." },
            ["include_files"] = new() { Type = "boolean", Description = "Whether to include files. Default: true." },
            ["pattern"] = new() { Type = "string", Description = "File extension filter (e.g. '.cs', '.py'). Optional, shows all files if omitted." },
        },
        Required = []
    };

    // 무시할 디렉토리 (빌드 산출물, 패키지 캐시 등)
    private static readonly HashSet<string> IgnoredDirs = new(StringComparer.OrdinalIgnoreCase)
    {
        "bin", "obj", "node_modules", ".git", ".vs", ".idea", ".vscode",
        "__pycache__", ".mypy_cache", ".pytest_cache", "dist", "build",
        "packages", ".nuget", "TestResults", "coverage", ".next",
        "target", ".gradle", ".cargo",
    };

    private const int MaxEntries = 500;

    public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct)
    {
        var subPath = args.TryGetProperty("path", out var p) ? p.GetString() ?? "" : "";
        var depth = 3;
        if (args.TryGetProperty("depth", out var d))
        {
            if (d.ValueKind == JsonValueKind.Number) depth = d.GetInt32();
            else if (d.ValueKind == JsonValueKind.String && int.TryParse(d.GetString(), out var dv)) depth = dv;
        }
        var depthStr = depth.ToString();
        var includeFiles = true;
        if (args.TryGetProperty("include_files", out var inc))
        {
            if (inc.ValueKind == JsonValueKind.True || inc.ValueKind == JsonValueKind.False)
                includeFiles = inc.GetBoolean();
            else
                includeFiles = !string.Equals(inc.GetString(), "false", StringComparison.OrdinalIgnoreCase);
        }
        var extFilter = args.TryGetProperty("pattern", out var pat) ? pat.GetString() ?? "" : "";

        if (!int.TryParse(depthStr, out var maxDepth) || maxDepth < 1)
            maxDepth = 3;
        maxDepth = Math.Min(maxDepth, 10);

        var baseDir = string.IsNullOrEmpty(subPath)
            ? context.WorkFolder
            : FileReadTool.ResolvePath(subPath, context.WorkFolder);

        if (string.IsNullOrEmpty(baseDir) || !Directory.Exists(baseDir))
            return Task.FromResult(ToolResult.Fail($"디렉토리가 존재하지 않습니다: {baseDir}"));

        if (!context.IsPathAllowed(baseDir))
            return Task.FromResult(ToolResult.Fail($"경로 접근 차단: {baseDir}"));

        try
        {
            var sb = new StringBuilder();
            var dirName = Path.GetFileName(baseDir);
            if (string.IsNullOrEmpty(dirName)) dirName = baseDir;
            sb.AppendLine($"{dirName}/");

            int entryCount = 0;
            BuildTree(sb, baseDir, "", 0, maxDepth, includeFiles, extFilter, context, ref entryCount);

            if (entryCount >= MaxEntries)
                sb.AppendLine($"\n... ({MaxEntries}개 항목 제한 도달, depth 또는 pattern을 조정하세요)");

            var summary = $"폴더 맵 생성 완료 ({entryCount}개 항목, 깊이 {maxDepth})";
            return Task.FromResult(ToolResult.Ok($"{summary}\n\n{sb}"));
        }
        catch (Exception ex)
        {
            return Task.FromResult(ToolResult.Fail($"폴더 맵 생성 실패: {ex.Message}"));
        }
    }

    private static void BuildTree(
        StringBuilder sb, string dir, string prefix, int currentDepth, int maxDepth,
        bool includeFiles, string extFilter, AgentContext context, ref int entryCount)
    {
        if (currentDepth >= maxDepth || entryCount >= MaxEntries) return;

        // 하위 디렉토리
        List<DirectoryInfo> subDirs;
        try
        {
            subDirs = new DirectoryInfo(dir).GetDirectories()
                .Where(d => !d.Attributes.HasFlag(FileAttributes.Hidden)
                         && !IgnoredDirs.Contains(d.Name))
                .OrderBy(d => d.Name)
                .ToList();
        }
        catch (Exception) { return; } // 접근 불가 디렉토리 무시

        // 하위 파일
        List<FileInfo> files = [];
        if (includeFiles)
        {
            try
            {
                files = new DirectoryInfo(dir).GetFiles()
                    .Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden)
                             && (string.IsNullOrEmpty(extFilter)
                                 || f.Extension.Equals(extFilter, StringComparison.OrdinalIgnoreCase)))
                    .OrderBy(f => f.Name)
                    .ToList();
            }
            catch (Exception) { /* ignore */ }
        }

        var totalItems = subDirs.Count + files.Count;
        var index = 0;

        // 디렉토리 출력
        foreach (var sub in subDirs)
        {
            if (entryCount >= MaxEntries) break;
            index++;
            var isLast = index == totalItems;
            var connector = isLast ? "└── " : "├── ";
            var childPrefix = isLast ? "    " : "│   ";

            sb.AppendLine($"{prefix}{connector}{sub.Name}/");
            entryCount++;

            if (context.IsPathAllowed(sub.FullName))
                BuildTree(sb, sub.FullName, prefix + childPrefix, currentDepth + 1, maxDepth,
                    includeFiles, extFilter, context, ref entryCount);
        }

        // 파일 출력
        foreach (var file in files)
        {
            if (entryCount >= MaxEntries) break;
            index++;
            var isLast = index == totalItems;
            var connector = isLast ? "└── " : "├── ";

            var sizeStr = FormatSize(file.Length);
            sb.AppendLine($"{prefix}{connector}{file.Name}  ({sizeStr})");
            entryCount++;
        }
    }

    private static string FormatSize(long bytes) => bytes switch
    {
        < 1024 => $"{bytes} B",
        < 1024 * 1024 => $"{bytes / 1024.0:F1} KB",
        _ => $"{bytes / (1024.0 * 1024.0):F1} MB",
    };
}
