using Microsoft.Extensions.Logging;
namespace JdeScoping.ConfigManager.Services;
///
/// Service for auto-discovering configuration file locations.
/// Searches in prioritized order:
/// 1. JDESCOPING_CONFIG_PATH environment variable
/// 2. Same directory as executable
/// 3. ../JdeScoping.Host/ relative to executable
/// 4. User config directory (~/.jdescoping on Unix, %LOCALAPPDATA%\JdeScoping on Windows)
///
public class AutoDiscoveryService : IAutoDiscoveryService
{
private readonly IFileSystem _fileSystem;
private readonly ILogger? _logger;
private const string EnvVarName = "JDESCOPING_CONFIG_PATH";
private const string AppSettingsFileName = "appsettings.json";
///
/// Initializes a new instance of the class.
///
/// The file system abstraction to use for directory and file checks.
/// Optional logger for recording discovery process information.
public AutoDiscoveryService(IFileSystem fileSystem, ILogger? logger = null)
{
_fileSystem = fileSystem;
_logger = logger;
}
///
/// Finds the configuration folder using a prioritized search strategy.
///
/// Cancellation token for the async operation.
/// The path to a valid configuration folder, or null if none is found.
public Task FindConfigFolderAsync(CancellationToken ct = default)
{
// 1. Check environment variable
var envPath = Environment.GetEnvironmentVariable(EnvVarName);
if (!string.IsNullOrEmpty(envPath) && IsValidConfigFolder(envPath))
{
_logger?.LogInformation("Found config folder from environment variable: {Path}", envPath);
return Task.FromResult(envPath);
}
// 2. Check same directory as executable
var exeDir = AppContext.BaseDirectory;
if (IsValidConfigFolder(exeDir))
{
_logger?.LogInformation("Found config folder in executable directory: {Path}", exeDir);
return Task.FromResult(exeDir);
}
// 3. Check ../JdeScoping.Host/ relative to executable
var hostDir = _fileSystem.Combine(exeDir, "..", "JdeScoping.Host");
if (IsValidConfigFolder(hostDir))
{
_logger?.LogInformation("Found config folder in host directory: {Path}", hostDir);
return Task.FromResult(hostDir);
}
// 4. Check project structure paths (for development)
// When running from bin/Debug/net10.0, go up to find src/JdeScoping.Host
var projectHostPaths = new[]
{
// From bin/Debug/net10.0 -> src/JdeScoping.Host
_fileSystem.Combine(exeDir, "..", "..", "..", "..", "..", "JdeScoping.Host"),
// Absolute fallback for development
"/Users/dohertj2/Desktop/JdeScopingTool/NEW/src/JdeScoping.Host"
};
foreach (var projectPath in projectHostPaths)
{
if (IsValidConfigFolder(projectPath))
{
_logger?.LogInformation("Found config folder in project directory: {Path}", projectPath);
return Task.FromResult(Path.GetFullPath(projectPath));
}
}
// 5. Check user config directory
var userConfigDir = GetUserConfigDirectory();
if (userConfigDir != null && IsValidConfigFolder(userConfigDir))
{
_logger?.LogInformation("Found config folder in user directory: {Path}", userConfigDir);
return Task.FromResult(userConfigDir);
}
_logger?.LogWarning("Could not find config folder in any standard location");
return Task.FromResult(null);
}
private bool IsValidConfigFolder(string path)
{
if (!_fileSystem.DirectoryExists(path))
return false;
var appSettingsPath = _fileSystem.Combine(path, AppSettingsFileName);
return _fileSystem.FileExists(appSettingsPath);
}
private string? GetUserConfigDirectory()
{
if (OperatingSystem.IsWindows())
{
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
return _fileSystem.Combine(localAppData, "JdeScoping");
}
else
{
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
return _fileSystem.Combine(home, ".jdescoping");
}
}
}