using System.Globalization;
using ClaudeDo.Data.Repositories;

namespace ClaudeDo.Worker.External;

/// <summary>
/// Resolves a task-id MCP parameter that may be a display number (<c>#123</c> or bare <c>123</c>)
/// into the task's GUID. A GUID is never all-digits, so the two forms never collide. Anything
/// else — including an empty string, which <c>update_task</c>'s dependsOnTaskId uses as a
/// "clear the link" sentinel — passes through untouched.
/// </summary>
internal static class TaskIdResolver
{
    public static async Task<string> ResolveAsync(TaskRepository tasks, string idOrNumber, CancellationToken ct = default)
    {
        return (await ResolveCoreAsync(tasks, idOrNumber, ct))!;
    }

    public static async Task<string?> ResolveOptionalAsync(TaskRepository tasks, string? idOrNumber, CancellationToken ct = default)
    {
        return idOrNumber is null ? null : await ResolveCoreAsync(tasks, idOrNumber, ct);
    }

    public static async Task<IReadOnlyList<string>> ResolveManyAsync(
        TaskRepository tasks, IReadOnlyList<string> idsOrNumbers, CancellationToken ct = default)
    {
        var resolved = new string[idsOrNumbers.Count];
        for (var i = 0; i < idsOrNumbers.Count; i++)
            resolved[i] = await ResolveAsync(tasks, idsOrNumbers[i], ct);
        return resolved;
    }

    private static async Task<string?> ResolveCoreAsync(TaskRepository tasks, string idOrNumber, CancellationToken ct)
    {
        if (idOrNumber.Length == 0)
            return idOrNumber;

        var candidate = idOrNumber[0] == '#' ? idOrNumber[1..] : idOrNumber;
        if (!int.TryParse(candidate, NumberStyles.None, CultureInfo.InvariantCulture, out var number))
            return idOrNumber;

        var task = await tasks.GetByNumberAsync(number, ct)
            ?? throw new InvalidOperationException($"no task with number {number}");
        return task.Id;
    }
}
