import type {
  ChildTaskRecord,
  VerdictRecord,
  WorkflowName,
  WorkflowPhaseName,
} from "@best-agent/protocol";
import { describe, expect, it } from "vitest";
import {
  buildWorkflowChildTaskGraph,
  buildWorkflowChildTaskGraphNodes,
  buildWorkflowPhaseResultGraph,
  buildWorkflowPhaseDependencies,
  buildWorkflowPhaseEdges,
  collectChildTaskEffectiveVerdicts,
  collectWorkflowTurnStates,
  collectWorkflowVerifyVerdicts,
  groupWorkflowPhaseRecordsByTurn,
  inferWorkflowNameFromPhaseResults,
  listWorkflowVerifyChildTaskIds,
  resolveWorkflowNodeAllowedTools,
  resolveReadyWorkflowPhases,
  resolveWorkflowPhaseLayers,
} from "./workflow-graph.js";

describe("workflow graph helpers", () => {
  it("resolves workflow phase layers deterministically", () => {
    expect(resolveWorkflowPhaseLayers("build-verify")).toEqual([["build"], ["verify"]]);
    expect(resolveWorkflowPhaseLayers("plan-build-verify")).toEqual([
      ["plan"],
      ["build"],
      ["verify"],
    ]);
    expect(resolveWorkflowPhaseLayers("plan-explore-build-verify")).toEqual([
      ["plan", "explore"],
      ["build"],
      ["verify"],
    ]);
  });

  it("infers workflow names from available phase results", () => {
    expect(
      inferWorkflowNameFromPhaseResults({
        build: "build",
        verify: "verify",
      }),
    ).toBe("build-verify");
    expect(
      inferWorkflowNameFromPhaseResults({
        explore: "explore",
        build: "build",
      }),
    ).toBe("explore-build");
    expect(
      inferWorkflowNameFromPhaseResults({
        plan: "plan",
        build: "build",
        verify: "verify",
      }),
    ).toBe("plan-build-verify");
    expect(
      inferWorkflowNameFromPhaseResults({
        plan: "plan",
        explore: "explore",
        build: "build",
        verify: "verify",
      }),
    ).toBe("plan-explore-build-verify");
  });

  it("builds dependency maps from workflow layers", () => {
    const layers = resolveWorkflowPhaseLayers("plan-explore-build-verify");
    const dependencies = buildWorkflowPhaseDependencies(layers);
    expect(dependencies.get("plan")).toEqual([]);
    expect(dependencies.get("explore")).toEqual([]);
    expect(dependencies.get("build")).toEqual(["plan", "explore"]);
    expect(dependencies.get("verify")).toEqual(["build"]);
  });

  it("builds graph edges from workflow layers", () => {
    const layers = resolveWorkflowPhaseLayers("plan-explore-build-verify");
    expect(buildWorkflowPhaseEdges(layers)).toEqual([
      { from: "plan", to: "build" },
      { from: "explore", to: "build" },
      { from: "build", to: "verify" },
    ]);
  });

  it("resolves ready phases from pending and completed phase sets", () => {
    const layers = resolveWorkflowPhaseLayers("plan-explore-build-verify");
    expect(
      resolveReadyWorkflowPhases({
        completedPhases: new Set(),
        pendingPhases: new Set(["plan", "explore", "build", "verify"]),
        workflowLayers: layers,
      }),
    ).toEqual(["plan", "explore"]);
    expect(
      resolveReadyWorkflowPhases({
        completedPhases: new Set(["plan", "explore"]),
        pendingPhases: new Set(["build", "verify"]),
        workflowLayers: layers,
      }),
    ).toEqual(["build"]);
    expect(
      resolveReadyWorkflowPhases({
        completedPhases: new Set(["plan", "explore", "build"]),
        pendingPhases: new Set(["verify"]),
        workflowLayers: layers,
      }),
    ).toEqual(["verify"]);
  });

  it("resolves workflow node allowed tools from the profile when present and falls back to the phase", () => {
    const allowedToolsByProfile = new Map<string, string[]>([
      ["build", ["write-workspace", "shell"]],
      ["custom-build", ["write-workspace", "apply-patch"]],
    ]);

    expect(
      resolveWorkflowNodeAllowedTools({
        fallbackPhaseName: "build",
        profileName: "custom-build",
        readProfileAllowedTools: (profileName) => allowedToolsByProfile.get(profileName),
      }),
    ).toEqual(["write-workspace", "apply-patch"]);

    expect(
      resolveWorkflowNodeAllowedTools({
        fallbackPhaseName: "build",
        profileName: "unknown-custom-build",
        readProfileAllowedTools: (profileName) => allowedToolsByProfile.get(profileName),
      }),
    ).toEqual(["write-workspace", "shell"]);
  });

  it("builds workflow child-task graph nodes with shared verdict and status projection", () => {
    const allowedToolsByProfile = new Map<string, string[]>([
      ["plan", ["ask-user"]],
      ["verify", ["read-workspace"]],
      ["custom-plan", ["read-workspace", "ask-user"]],
    ]);
    const effectiveVerdict: VerdictRecord = {
      applicationState: "applied" as const,
      createdAt: "2026-04-10T12:00:00.000Z",
      graphId: "workflow:plan-build-verify",
      graphRevision: 1,
      nodeId: "child-plan",
      source: "deterministic" as const,
      status: "satisfied" as const,
      summary: "plan satisfied",
      supportsEvidenceIds: [],
      verdictId: "verdict_plan",
    };
    const phases = new Map<
      WorkflowPhaseName,
      ChildTaskRecord & {
        phaseName: WorkflowPhaseName;
        workflowName: WorkflowName;
      }
    >([
      [
        "plan",
        {
          childTaskId: "child-plan" as ChildTaskRecord["childTaskId"],
          childThreadId: "thread_child_plan" as NonNullable<ChildTaskRecord["childThreadId"]>,
          correlationId: "corr-plan",
          createdAt: "2026-04-10T12:00:00.000Z",
          joinPolicy: "aggregate-evidence" as const,
          objective: "plan it",
          ownershipMode: "read-only" as const,
          parentTurnId: "turn_parent" as ChildTaskRecord["parentTurnId"],
          phaseName: "plan" as const,
          profileName: "custom-plan",
          status: "joined" as const,
          threadId: "thread_123" as ChildTaskRecord["threadId"],
          turnId: "turn_parent" as ChildTaskRecord["turnId"],
          workflowName: "plan-build-verify" as const,
        },
      ],
      [
        "verify",
        {
          childTaskId: "child-verify" as ChildTaskRecord["childTaskId"],
          correlationId: "corr-verify",
          createdAt: "2026-04-10T12:01:00.000Z",
          joinPolicy: "aggregate-verdicts" as const,
          objective: "verify it",
          ownershipMode: "read-only" as const,
          parentTurnId: "turn_parent" as ChildTaskRecord["parentTurnId"],
          phaseName: "verify" as const,
          profileName: "verify",
          status: "running" as const,
          threadId: "thread_123" as ChildTaskRecord["threadId"],
          turnId: "turn_parent" as ChildTaskRecord["turnId"],
          workflowName: "plan-build-verify" as const,
        },
      ],
    ]);

    expect(
      buildWorkflowChildTaskGraphNodes({
        effectiveVerdicts: new Map([["child-plan", effectiveVerdict]]),
        phases,
        readProfileAllowedTools: (profileName) => allowedToolsByProfile.get(profileName),
        statusForRecord: (record) =>
          record?.status === "joined" ? "completed" : (record?.status ?? "pending"),
        workflowName: "plan-build-verify",
      }),
    ).toEqual([
      {
        allowedTools: ["read-workspace", "ask-user"],
        childThreadId: "thread_child_plan",
        correlationId: "corr-plan",
        effectiveVerdict,
        nodeId: "child-plan",
        phaseName: "plan",
        profileName: "custom-plan",
        status: "completed",
      },
      {
        allowedTools: [],
        phaseName: "build",
        profileName: "build",
        status: "pending",
      },
      {
        allowedTools: ["read-workspace"],
        correlationId: "corr-verify",
        nodeId: "child-verify",
        phaseName: "verify",
        profileName: "verify",
        status: "running",
      },
    ]);
  });

  it("groups workflow phase records by turn while keeping the latest record per phase", () => {
    const records: ChildTaskRecord[] = [
      {
        childTaskId: "verify-old" as ChildTaskRecord["childTaskId"],
        correlationId: "corr-verify-old",
        createdAt: "2026-04-10T12:00:00.000Z",
        joinPolicy: "aggregate-verdicts",
        objective: "verify-old",
        ownershipMode: "read-only",
        parentTurnId: "turn-latest" as ChildTaskRecord["parentTurnId"],
        phaseName: "verify",
        profileName: "verify",
        status: "failed",
        threadId: "thread_123" as ChildTaskRecord["threadId"],
        turnId: "turn-latest" as ChildTaskRecord["turnId"],
        workflowName: "build-verify",
      },
      {
        childTaskId: "build-new" as ChildTaskRecord["childTaskId"],
        correlationId: "corr-build-new",
        createdAt: "2026-04-10T12:01:00.000Z",
        joinPolicy: "aggregate-evidence",
        objective: "build",
        ownershipMode: "read-only",
        parentTurnId: "turn-latest" as ChildTaskRecord["parentTurnId"],
        phaseName: "build",
        profileName: "build",
        status: "joined",
        threadId: "thread_123" as ChildTaskRecord["threadId"],
        turnId: "turn-latest" as ChildTaskRecord["turnId"],
        workflowName: "build-verify",
      },
      {
        childTaskId: "verify-new" as ChildTaskRecord["childTaskId"],
        correlationId: "corr-verify-new",
        createdAt: "2026-04-10T12:02:00.000Z",
        joinPolicy: "aggregate-verdicts",
        objective: "verify",
        ownershipMode: "read-only",
        parentTurnId: "turn-latest" as ChildTaskRecord["parentTurnId"],
        phaseName: "verify",
        profileName: "verify",
        status: "completed",
        threadId: "thread_123" as ChildTaskRecord["threadId"],
        turnId: "turn-latest" as ChildTaskRecord["turnId"],
        workflowName: "build-verify",
      },
      {
        childTaskId: "verify-new" as ChildTaskRecord["childTaskId"],
        correlationId: "corr-verify-new",
        createdAt: "2026-04-10T12:03:00.000Z",
        joinPolicy: "aggregate-verdicts",
        objective: "verify duplicate running projection",
        ownershipMode: "read-only",
        parentTurnId: "turn-latest" as ChildTaskRecord["parentTurnId"],
        phaseName: "verify",
        profileName: "verify",
        status: "running",
        threadId: "thread_123" as ChildTaskRecord["threadId"],
        turnId: "turn-latest" as ChildTaskRecord["turnId"],
        workflowName: "build-verify",
      },
      {
        childTaskId: "build-prior" as ChildTaskRecord["childTaskId"],
        correlationId: "corr-build-prior",
        createdAt: "2026-04-10T11:59:00.000Z",
        joinPolicy: "aggregate-evidence",
        objective: "build-prior",
        ownershipMode: "read-only",
        parentTurnId: "turn-prior" as ChildTaskRecord["parentTurnId"],
        phaseName: "build",
        profileName: "build",
        status: "completed",
        threadId: "thread_123" as ChildTaskRecord["threadId"],
        turnId: "turn-prior" as ChildTaskRecord["turnId"],
        workflowName: "build-verify",
      },
      {
        childTaskId: "ignored" as ChildTaskRecord["childTaskId"],
        correlationId: "corr-ignored",
        createdAt: "2026-04-10T11:58:00.000Z",
        joinPolicy: "aggregate-evidence",
        objective: "ignored",
        ownershipMode: "read-only",
        parentTurnId: "turn-prior" as ChildTaskRecord["parentTurnId"],
        profileName: "build",
        status: "completed",
        threadId: "thread_123" as ChildTaskRecord["threadId"],
        turnId: "turn-prior" as ChildTaskRecord["turnId"],
      },
    ];

    const grouped = groupWorkflowPhaseRecordsByTurn(records);
    expect(Array.from(grouped.keys())).toEqual(["turn-latest", "turn-prior"]);
    expect(
      grouped.get("turn-latest" as ChildTaskRecord["turnId"])?.get("verify")?.childTaskId,
    ).toBe("verify-new");
    expect(grouped.get("turn-latest" as ChildTaskRecord["turnId"])?.get("build")?.childTaskId).toBe(
      "build-new",
    );
    expect(
      listWorkflowVerifyChildTaskIds(
        grouped.get("turn-latest" as ChildTaskRecord["turnId"]) ?? new Map(),
      ),
    ).toEqual(["verify-new"]);
  });

  it("builds workflow child-task graphs with shared edges and nodes", () => {
    const phases = new Map<
      WorkflowPhaseName,
      ChildTaskRecord & {
        phaseName: WorkflowPhaseName;
        workflowName: WorkflowName;
      }
    >([
      [
        "build",
        {
          childTaskId: "child-build" as ChildTaskRecord["childTaskId"],
          correlationId: "corr-build",
          createdAt: "2026-04-10T12:00:00.000Z",
          joinPolicy: "aggregate-evidence",
          objective: "build",
          ownershipMode: "read-only",
          parentTurnId: "turn_parent" as ChildTaskRecord["parentTurnId"],
          phaseName: "build",
          profileName: "build",
          status: "joined",
          threadId: "thread_123" as ChildTaskRecord["threadId"],
          turnId: "turn_parent" as ChildTaskRecord["turnId"],
          workflowName: "build-verify",
        },
      ],
    ]);

    expect(
      buildWorkflowChildTaskGraph({
        phases,
        readProfileAllowedTools: (profileName) =>
          profileName === "build" ? ["write-workspace"] : ["read-workspace"],
        statusForRecord: (record) =>
          record?.status === "joined" ? "completed" : (record?.status ?? "pending"),
        workflowName: "build-verify",
      }),
    ).toEqual({
      edges: [{ from: "build", to: "verify" }],
      nodes: [
        {
          allowedTools: ["write-workspace"],
          correlationId: "corr-build",
          nodeId: "child-build",
          phaseName: "build",
          profileName: "build",
          status: "completed",
        },
        {
          allowedTools: ["read-workspace"],
          phaseName: "verify",
          profileName: "verify",
          status: "pending",
        },
      ],
    });
  });

  it("builds workflow phase-result graphs with shared topology and tool fallback", () => {
    expect(
      buildWorkflowPhaseResultGraph({
        phaseResults: {
          build: "build done",
        },
        readProfileAllowedTools: (profileName) => {
          if (profileName === "build") {
            return ["write-workspace"];
          }
          if (profileName === "verify") {
            return ["read-workspace"];
          }
          return [];
        },
        workflowName: "build-verify",
      }),
    ).toEqual({
      edges: [{ from: "build", to: "verify" }],
      nodes: [
        {
          allowedTools: ["write-workspace"],
          phaseName: "build",
          profileName: "build",
          status: "completed",
        },
        {
          allowedTools: ["read-workspace"],
          phaseName: "verify",
          profileName: "verify",
          status: "pending",
        },
      ],
    });
  });

  it("collects effective verdicts for workflow verify child tasks only", async () => {
    const phases = new Map<
      WorkflowPhaseName,
      ChildTaskRecord & {
        phaseName: WorkflowPhaseName;
        workflowName: WorkflowName;
      }
    >([
      [
        "build",
        {
          childTaskId: "child-build" as ChildTaskRecord["childTaskId"],
          correlationId: "corr-build",
          createdAt: "2026-04-10T12:00:00.000Z",
          joinPolicy: "aggregate-evidence",
          objective: "build",
          ownershipMode: "read-only",
          parentTurnId: "turn_parent" as ChildTaskRecord["parentTurnId"],
          phaseName: "build",
          profileName: "build",
          status: "joined",
          threadId: "thread_123" as ChildTaskRecord["threadId"],
          turnId: "turn_parent" as ChildTaskRecord["turnId"],
          workflowName: "build-verify",
        },
      ],
      [
        "verify",
        {
          childTaskId: "child-verify" as ChildTaskRecord["childTaskId"],
          correlationId: "corr-verify",
          createdAt: "2026-04-10T12:01:00.000Z",
          joinPolicy: "aggregate-verdicts",
          objective: "verify",
          ownershipMode: "read-only",
          parentTurnId: "turn_parent" as ChildTaskRecord["parentTurnId"],
          phaseName: "verify",
          profileName: "verify",
          status: "joined",
          threadId: "thread_123" as ChildTaskRecord["threadId"],
          turnId: "turn_parent" as ChildTaskRecord["turnId"],
          workflowName: "build-verify",
        },
      ],
    ]);
    const verdict: VerdictRecord = {
      applicationState: "applied",
      createdAt: "2026-04-10T12:02:00.000Z",
      graphId: "workflow:build-verify",
      graphRevision: 1,
      nodeId: "child-verify",
      source: "deterministic",
      status: "satisfied",
      summary: "verify satisfied",
      supportsEvidenceIds: [],
      verdictId: "verdict-verify",
    };

    const result = await collectWorkflowVerifyVerdicts({
      effectiveVerdicts: new Map([["child-verify", verdict]]),
      phases,
    });

    expect(result).toEqual(new Map([["child-verify", verdict]]));
  });

  it("collects effective verdicts for the provided child task ids", async () => {
    const verdict: VerdictRecord = {
      applicationState: "applied",
      createdAt: "2026-04-10T12:02:00.000Z",
      graphId: "workflow:build-verify",
      graphRevision: 1,
      nodeId: "child-verify",
      source: "deterministic",
      status: "satisfied",
      summary: "verify satisfied",
      supportsEvidenceIds: [],
      verdictId: "verdict-verify",
    };

    const result = await collectChildTaskEffectiveVerdicts({
      childTaskIds: [
        "child-build" as ChildTaskRecord["childTaskId"],
        "child-verify" as ChildTaskRecord["childTaskId"],
      ],
      effectiveVerdicts: new Map([["child-verify", verdict]]),
    });

    expect(result).toEqual(new Map([["child-verify", verdict]]));
  });

  it("collects workflow turn states with latest phase records and verify verdicts", async () => {
    const records: ChildTaskRecord[] = [
      {
        childTaskId: "verify-latest" as ChildTaskRecord["childTaskId"],
        correlationId: "corr-verify-latest",
        createdAt: "2026-04-10T12:03:00.000Z",
        joinPolicy: "aggregate-verdicts",
        objective: "verify latest",
        ownershipMode: "read-only",
        parentTurnId: "turn-latest" as ChildTaskRecord["parentTurnId"],
        phaseName: "verify",
        profileName: "verify",
        status: "joined",
        threadId: "thread_123" as ChildTaskRecord["threadId"],
        turnId: "turn-latest" as ChildTaskRecord["turnId"],
        workflowName: "build-verify",
      },
      {
        childTaskId: "build-latest" as ChildTaskRecord["childTaskId"],
        correlationId: "corr-build-latest",
        createdAt: "2026-04-10T12:02:00.000Z",
        joinPolicy: "aggregate-evidence",
        objective: "build latest",
        ownershipMode: "read-only",
        parentTurnId: "turn-latest" as ChildTaskRecord["parentTurnId"],
        phaseName: "build",
        profileName: "build",
        status: "joined",
        threadId: "thread_123" as ChildTaskRecord["threadId"],
        turnId: "turn-latest" as ChildTaskRecord["turnId"],
        workflowName: "build-verify",
      },
      {
        childTaskId: "verify-prior" as ChildTaskRecord["childTaskId"],
        correlationId: "corr-verify-prior",
        createdAt: "2026-04-10T12:01:00.000Z",
        joinPolicy: "aggregate-verdicts",
        objective: "verify prior",
        ownershipMode: "read-only",
        parentTurnId: "turn-prior" as ChildTaskRecord["parentTurnId"],
        phaseName: "verify",
        profileName: "verify",
        status: "joined",
        threadId: "thread_123" as ChildTaskRecord["threadId"],
        turnId: "turn-prior" as ChildTaskRecord["turnId"],
        workflowName: "build-verify",
      },
    ];

    const workflowTurns = await collectWorkflowTurnStates({
      effectiveVerdicts: new Map([
        [
          "verify-latest",
          {
            applicationState: "applied",
            createdAt: "2026-04-10T12:04:00.000Z",
            graphId: "workflow:build-verify",
            graphRevision: 1,
            nodeId: "verify-latest" as ChildTaskRecord["childTaskId"],
            source: "deterministic",
            status: "satisfied",
            summary: "verify latest passed",
            supportsEvidenceIds: [],
            verdictId: "verdict-latest",
          },
        ],
      ]),
      records,
    });

    expect(workflowTurns).toHaveLength(2);
    expect(workflowTurns[0]).toMatchObject({
      turnId: "turn-latest",
      workflowName: "build-verify",
    });
    expect(Array.from(workflowTurns[0].phases.keys())).toEqual(["verify", "build"]);
    expect(workflowTurns[0].effectiveVerdicts.get("verify-latest")?.summary).toBe(
      "verify latest passed",
    );
    expect(workflowTurns[1]).toMatchObject({
      turnId: "turn-prior",
      workflowName: "build-verify",
    });
    expect(workflowTurns[1].effectiveVerdicts.size).toBe(0);
  });
});
