import assert from "node:assert/strict";
import { describe, it } from "node:test";

import { classifyTaskDispatchStall } from "../TaskDispatchStall.ts";

describe("classifyTaskDispatchStall", () => {
  it("distinguishes dependency wait before offering recovery", () => {
    assert.deepEqual(
      classifyTaskDispatchStall({
        lifecycleBucket: "inbox",
        fmState: "inbox",
        dependencyAllowed: false,
      }),
      {
        state: "waiting_dependency",
        actionable: false,
        recommended_action: "wait_dependency",
        reason: "task dependencies are not settled",
      },
    );
  });

  it("classifies inbox/dispatched as a repairable lifecycle split", () => {
    const result = classifyTaskDispatchStall({
      lifecycleBucket: "inbox",
      fmState: "dispatched",
      dependencyAllowed: true,
    });
    assert.equal(result.state, "lifecycle_split");
    assert.equal(result.recommended_action, "repair_retry");
  });

  it("offers autonomous claim for an unclaimed recipient inbox task", () => {
    const result = classifyTaskDispatchStall({
      lifecycleBucket: "inbox",
      fmState: "inbox",
      dependencyAllowed: true,
    });
    assert.equal(result.state, "task_unclaimed");
    assert.equal(result.recommended_action, "claim_task");
  });

  it("separates running, startup settling, and projection conflict", () => {
    const lease = {
      lease_id: "lease-1",
      task_id: "TASK-1",
      attempt_id: "attempt-1",
      agent_id: "DEV-01",
      session_id: "session-1",
      acquired_at: "2026-08-03T00:00:00.000Z",
      expires_at: "2026-08-03T01:00:00.000Z",
    };
    assert.equal(
      classifyTaskDispatchStall({
        lifecycleBucket: "active",
        activeLease: lease,
        hasLiveSession: true,
      }).state,
      "running",
    );
    const settling = classifyTaskDispatchStall({
      lifecycleBucket: "active",
      activeLease: lease,
      hasLiveSession: false,
      nowMs: Date.parse("2026-08-03T00:00:10.000Z"),
    });
    assert.equal(settling.state, "settling");
    assert.equal(settling.recommended_action, "none");

    const conflict = classifyTaskDispatchStall({
      lifecycleBucket: "active",
      activeLease: lease,
      hasLiveSession: false,
      nowMs: Date.parse("2026-08-03T00:10:00.000Z"),
    });
    assert.equal(conflict.state, "projection_conflict");
    assert.equal(conflict.recommended_action, "inspect_conflict");
  });

  it("consumes the canonical state before local stall hints", () => {
    const conflict = classifyTaskDispatchStall({
      lifecycleBucket: "active",
      hasLiveSession: false,
      canonicalState: "projection_conflict",
    });
    assert.equal(conflict.state, "projection_conflict");
    assert.equal(conflict.actionable, false);
    const running = classifyTaskDispatchStall({
      lifecycleBucket: "active",
      hasLiveSession: false,
      canonicalState: "running",
    });
    assert.equal(running.state, "running");
    for (const state of [
      "waiting_capacity",
      "waiting_report",
      "failed",
      "blocked",
      "done",
    ] as const) {
      const projected = classifyTaskDispatchStall({
        lifecycleBucket: "active",
        hasLiveSession: false,
        canonicalState: state,
      });
      assert.equal(projected.state, state);
      assert.equal(projected.actionable, false);
    }
  });
});
