using System.Text.Json;

namespace Sdt.Config;

public static class WorkspaceLoader
{
    public const string FileName = "sdt-workspace.json";

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        AllowTrailingCommas = true,
        ReadCommentHandling = JsonCommentHandling.Skip,
    };

    /// <summary>
    /// Walks up from <paramref name="startDir"/> (or CWD) to find sdt-workspace.json.
    /// Returns null if not found.
    /// </summary>
    public static (WorkspaceConfig Config, string WorkspaceRoot)? FindAndLoad(string? startDir = null)
    {
        var dir = new DirectoryInfo(startDir ?? Directory.GetCurrentDirectory());
        while (dir is not null)
        {
            var candidate = Path.Combine(dir.FullName, FileName);
            if (File.Exists(candidate))
            {
                try
                {
                    var json = File.ReadAllText(candidate);
                    var config = JsonSerializer.Deserialize<WorkspaceConfig>(json, JsonOptions)
                        ?? throw new InvalidOperationException($"{FileName} deserialized to null.");
                    return (config, dir.FullName);
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException(
                        $"Failed to parse {FileName} at {candidate}: {ex.Message}", ex);
                }
            }
            dir = dir.Parent!;
        }

        // No workspace file found; synthesize one by scanning nearby project roots.
        return TryAutoDiscover(startDir ?? Directory.GetCurrentDirectory());
    }

    /// <summary>
    /// Resolves the absolute project root for a workspace project entry.
    /// </summary>
    public static string ResolveProjectRoot(string workspaceRoot, WorkspaceProject project)
        => Path.GetFullPath(Path.IsPathRooted(project.Path)
            ? project.Path
            : Path.Combine(workspaceRoot, project.Path));

    public static string GetWorkspaceFilePath(string workspaceRoot)
        => Path.Combine(workspaceRoot, FileName);

    public static void Save(string workspaceRoot, WorkspaceConfig workspace)
    {
        var path = GetWorkspaceFilePath(workspaceRoot);
        var saveOptions = new JsonSerializerOptions(JsonOptions)
        {
            WriteIndented = true
        };
        var json = JsonSerializer.Serialize(workspace, saveOptions);
        File.WriteAllText(path, json + Environment.NewLine);
    }

    private static (WorkspaceConfig Config, string WorkspaceRoot)? TryAutoDiscover(string startDir)
    {
        LoadedProjectConfig? loaded;
        try
        {
            loaded = ConfigLoader.FindAndLoad(startDir);
        }
        catch
        {
            return null;
        }

        if (loaded is null)
            return null;

        var currentRoot = loaded.ProjectRoot;
        var parent = Directory.GetParent(currentRoot);
        var workspaceRoot = parent?.FullName ?? currentRoot;

        var roots = DiscoverProjectRoots(workspaceRoot, currentRoot);
        if (roots.Count == 0)
            return null;

        var projects = new List<WorkspaceProject>();
        foreach (var root in roots)
        {
            LoadedProjectConfig? cfg;
            try
            {
                cfg = ConfigLoader.FindAndLoad(root);
            }
            catch
            {
                continue;
            }

            var name = cfg?.Config.Name;
            projects.Add(new WorkspaceProject
            {
                Name = string.IsNullOrWhiteSpace(name) ? new DirectoryInfo(root).Name : name!,
                Description = $"Auto-discovered at {root}",
                Path = Path.GetRelativePath(workspaceRoot, root),
            });
        }

        return (
            new WorkspaceConfig
            {
                Name = "SDT Auto Workspace",
                Projects = projects
            },
            workspaceRoot);
    }

    private static List<string> DiscoverProjectRoots(string workspaceRoot, string currentRoot)
    {
        var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var excluded = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            "bin",
            "obj",
            ".git",
            ".venv",
            "node_modules",
        };

        void AddIfProject(string path)
        {
            var full = Path.GetFullPath(path);
            if (File.Exists(Path.Combine(full, "devtool.json")))
                set.Add(full);
        }

        AddIfProject(currentRoot);
        AddIfProject(workspaceRoot);

        try
        {
            foreach (var dir in Directory.EnumerateDirectories(workspaceRoot))
            {
                if (excluded.Contains(Path.GetFileName(dir)))
                    continue;

                AddIfProject(dir);
                foreach (var sub in Directory.EnumerateDirectories(dir))
                {
                    if (excluded.Contains(Path.GetFileName(sub)))
                        continue;
                    AddIfProject(sub);
                }
            }
        }
        catch
        {
            // Ignore inaccessible directories during auto-discovery.
        }

        return set.OrderBy(p => p, StringComparer.OrdinalIgnoreCase).ToList();
    }
}
