using System.ComponentModel;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Runner;
using ModelContextProtocol.Server;

namespace ClaudeDo.Worker.External;

// Coerced=true means nextPhase asked for a non-final phase but the server-side round cap
// (HandoffRoundTracker) forced it to its "_final" counterpart instead -- NextPhase already
// reflects the effective (post-coercion) value in that case.
public sealed record HandoffListHandlerResult(bool Requested, string TaskId, int Number, int SurvivingCount, string NextPhase, bool Coerced = false);

[McpServerToolType]
public sealed class HandoffMcpTools
{
    private readonly TaskRepository _tasks;
    private readonly HubBroadcaster _broadcaster;
    private readonly HandoffRoundTracker _rounds;

    public HandoffMcpTools(TaskRepository tasks, HubBroadcaster broadcaster, HandoffRoundTracker rounds)
    {
        _tasks = tasks;
        _broadcaster = broadcaster;
        _rounds = rounds;
    }

    [McpServerTool, Description(
        "Call at the end of a list-handler (\"Let Claude handle it\") phase to hand this run off to a " +
        "fresh ConPTY session that carries out the next phase, without dragging along this session's " +
        "own context. Reuses the SAME handler task -- no new task is created, and HandlerBaseCommit is " +
        "untouched. nextPhase picks the next session's role: \"wait\" (queue + wait; the normal handoff " +
        "after triage) or \"merge\" (review + merge; the normal handoff after wait) -- \"wait_final\"/" +
        "\"merge_final\" are only for a merge session that started reruns and must eventually stop the " +
        "chain instead of restarting again. An unrecognized value is rejected. This handoff chain is " +
        "capped server-side at four handoffs (i.e. two " +
        "full wait/merge cycles) for a given handler task: once reached, a \"wait\"/\"merge\" request is " +
        "silently coerced to its \"_final\" counterpart instead (check the result's coerced field), and " +
        "any handoff at all after a merge_final round for this task throws. The current tile stays " +
        "open; you must end your own turn immediately after calling this.")]
    public async Task<HandoffListHandlerResult> HandoffListHandler(
        [Description("This session's own handler task id.")] string taskId,
        [Description("The tasks that made it past dedupe, in the order to run them.")] IReadOnlyList<string> survivingTaskIds,
        [Description("Next session's phase: wait | merge | wait_final | merge_final.")] string nextPhase = "wait",
        CancellationToken cancellationToken = default)
    {
        MergeHelperPhase.Validate(nextPhase);

        if (survivingTaskIds.Count == 0)
            throw new InvalidOperationException("survivingTaskIds must contain at least one task id.");

        taskId = await TaskIdResolver.ResolveAsync(_tasks, taskId, cancellationToken);
        survivingTaskIds = await TaskIdResolver.ResolveManyAsync(_tasks, survivingTaskIds, cancellationToken);

        var task = await _tasks.GetByIdAsync(taskId, cancellationToken)
            ?? throw new InvalidOperationException($"Task {taskId} not found.");

        foreach (var id in survivingTaskIds)
            _ = await _tasks.GetByIdAsync(id, cancellationToken)
                ?? throw new InvalidOperationException($"Task {id} not found.");

        var round = _rounds.Register(taskId, nextPhase);
        if (round.Rejected)
            throw new InvalidOperationException(round.Error);

        await _broadcaster.HandoffRequested(taskId, survivingTaskIds, round.EffectivePhase);
        return new HandoffListHandlerResult(true, taskId, task.Number, survivingTaskIds.Count, round.EffectivePhase, round.Coerced);
    }
}
