using SysEnvironment = System.Environment;

namespace ClaudeDo.Data.Environment;

public sealed record ResolvedExecutable(string Path, bool IsShim);

public sealed record ShimStartInfo(string FileName, string Arguments);

/// <summary>
/// Resolves a command the way Windows' CreateProcess/PATH search does, but also finds
/// non-.exe shims (.cmd/.bat/.ps1) that UseShellExecute=false alone would miss.
/// </summary>
public static class ExecutableResolver
{
    private const string DefaultPathExt = ".COM;.EXE;.BAT;.CMD";

    // Known npm/claude install locations to try when PATH search comes up empty.
    private static readonly string[] FallbackDirectoryTemplates =
    {
        "%APPDATA%\\npm",
        "%LOCALAPPDATA%\\Programs\\claude",
        "%USERPROFILE%\\.local\\bin",
    };

    public static ResolvedExecutable? Resolve(string command, string? pathOverride = null, string? pathExtOverride = null)
    {
        var pathExts = ParsePathExt(pathExtOverride);

        if (LooksLikePath(command))
        {
            return ResolveAsPath(command, pathExts);
        }

        var directories = ParsePath(pathOverride);
        foreach (var dir in directories)
        {
            var resolved = ResolveInDirectory(dir, command, pathExts);
            if (resolved is not null) return resolved;
        }

        foreach (var template in FallbackDirectoryTemplates)
        {
            var dir = SysEnvironment.ExpandEnvironmentVariables(template);
            var resolved = ResolveInDirectory(dir, command, pathExts);
            if (resolved is not null) return resolved;
        }

        return null;
    }

    /// <summary>Expanded fallback directories tried when PATH search comes up empty (for diagnostics).</summary>
    public static IReadOnlyList<string> FallbackDirectories() =>
        FallbackDirectoryTemplates.Select(SysEnvironment.ExpandEnvironmentVariables).ToList();

    public static ShimStartInfo BuildShimStartInfo(string shimPath, IReadOnlyList<string> arguments)
    {
        var parts = new List<string> { "/c", Quote(shimPath) };
        parts.AddRange(arguments.Select(Quote));
        return new ShimStartInfo("cmd.exe", string.Join(' ', parts));
    }

    private static bool LooksLikePath(string command) =>
        command.Contains(Path.DirectorySeparatorChar) || command.Contains(Path.AltDirectorySeparatorChar);

    private static ResolvedExecutable? ResolveAsPath(string command, IReadOnlyList<string> pathExts)
    {
        if (File.Exists(command)) return new ResolvedExecutable(command, IsShimExtension(Path.GetExtension(command)));

        if (Path.HasExtension(command)) return null;

        foreach (var ext in pathExts)
        {
            var candidate = command + ext;
            if (File.Exists(candidate)) return new ResolvedExecutable(candidate, IsShimExtension(ext));
        }

        return null;
    }

    private static ResolvedExecutable? ResolveInDirectory(string directory, string command, IReadOnlyList<string> pathExts)
    {
        if (!Directory.Exists(directory)) return null;

        if (Path.HasExtension(command))
        {
            var candidate = Path.Combine(directory, command);
            return File.Exists(candidate) ? new ResolvedExecutable(candidate, IsShimExtension(Path.GetExtension(candidate))) : null;
        }

        foreach (var ext in pathExts)
        {
            var candidate = Path.Combine(directory, command + ext);
            if (File.Exists(candidate)) return new ResolvedExecutable(candidate, IsShimExtension(ext));
        }

        return null;
    }

    private static bool IsShimExtension(string extension) =>
        !extension.Equals(".exe", StringComparison.OrdinalIgnoreCase)
        && !extension.Equals(".com", StringComparison.OrdinalIgnoreCase);

    private static IReadOnlyList<string> ParsePathExt(string? pathExtOverride)
    {
        var raw = pathExtOverride ?? SysEnvironment.GetEnvironmentVariable("PATHEXT") ?? DefaultPathExt;
        return raw.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
    }

    private static IReadOnlyList<string> ParsePath(string? pathOverride)
    {
        var raw = pathOverride ?? SysEnvironment.GetEnvironmentVariable("PATH") ?? "";
        return raw.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
    }

    private static string Quote(string value) => value.Contains(' ') ? $"\"{value}\"" : value;
}
