import { expect, vi } from "vitest";
import { createMockSpawn } from "./mock-spawn.js";
import { createMockTaskList } from "./mock-task-list.js";
const defaultState = "planned";
const defaultTaskId = "task-1";
const defaultTaskList = "tasks";
const defaultWorkspaceRoot = "/__memfs__/workspaces";
export function createTask(overrides = {}) {
    const parsed = parseQualifiedId(overrides.qualifiedId);
    const list = overrides.list ?? parsed?.list ?? defaultTaskList;
    const id = overrides.id ?? parsed?.id ?? defaultTaskId;
    const qualifiedId = overrides.qualifiedId ?? `${list}/${id}`;
    const name = overrides.name ?? (overrides.qualifiedId === undefined ? "Build runner" : id);
    const task = {
        ...overrides,
        list,
        id,
        qualifiedId,
        name,
        state: overrides.state ?? defaultState,
        description: overrides.description ?? "Render this task body",
        metadata: overrides.metadata ?? {},
        url: overrides.url
    };
    return task;
}
export function createConfig(overrides = {}) {
    const states = overrides.states ?? {
        planned: { prompt: "Plan {{ prompt }}" },
        done: { terminal: true }
    };
    return {
        tasks: { type: "markdown-dir", path: "/__memfs__/tasks" },
        polling: { intervalMs: 30_000 },
        workspace: { root: defaultWorkspaceRoot },
        ...withoutAgent(overrides),
        states,
        activeStateNames: overrides.activeStateNames ?? stateNamesBy(states, (state) => state.prompt !== undefined),
        terminalStateNames: overrides.terminalStateNames ?? stateNamesBy(states, (state) => state.terminal === true),
        stateOrder: overrides.stateOrder ?? Object.keys(states),
        agent: {
            service: "codex",
            list: defaultTaskList,
            maxConcurrentAgents: overrides.agent?.maxConcurrentAgents ?? overrides.maxConcurrentAgents ?? 1,
            maxRetryBackoffMs: 300_000,
            ...overrides.agent
        }
    };
}
export function createTickDeps(overrides = {}) {
    const mockSpawn = createMockSpawn();
    return {
        tasks: createMockTaskList(),
        validateDispatch: async () => ({ ok: true }),
        reconcileRunning: async () => [],
        ensureWorkspace: async (_root, qualifiedId) => ({
            path: `${defaultWorkspaceRoot}/${qualifiedId.replaceAll("/", "_")}`,
            createdNow: true
        }),
        runAttempt: async () => ({ reason: "abnormal", failure: "canceled" }),
        spawn: mockSpawn.spawn,
        now: () => 0,
        ...overrides
    };
}
export function createDriverContext(overrides = {}) {
    const events = overrides.events ?? [];
    const mockSpawn = createMockSpawn();
    return {
        task: createTask(),
        attempt: 1,
        workspaceDir: `${defaultWorkspaceRoot}/${defaultTaskId}`,
        planPath: "/__memfs__/tasks/task-1.md",
        cfg: createConfig(),
        abort: new AbortController().signal,
        emit: (event) => events.push(event),
        spawn: mockSpawn.spawn,
        logger: { warn: () => undefined },
        ...withoutEvents(overrides)
    };
}
export function createWorkflowDefinition(overrides = {}) {
    return {
        sourcePath: "/__memfs__/WORKFLOW.md",
        config: createConfig(),
        promptTemplate: "{{ task.description }}",
        ...overrides
    };
}
export function successSpawn(overrides = {}) {
    return { stdout: "", stderr: "", exitCode: 0, ...overrides };
}
export function assertNoLeakedWorkers(state, workers) {
    expect({
        workers: [...workers.keys()],
        running: [...state.running.keys()],
        claimed: [...state.claimed.values()]
    }).toEqual({
        workers: [],
        running: [],
        claimed: []
    });
}
export async function assertEventually(predicate, options) {
    for (let tickCount = 0; tickCount <= options.ticks; tickCount += 1) {
        if (await predicate()) {
            return tickCount;
        }
        if (tickCount === options.ticks) {
            break;
        }
        await vi.advanceTimersByTimeAsync(options.advanceMs ?? 1);
        await options.tick();
        await Promise.resolve();
    }
    expect(await predicate()).toBe(true);
    return options.ticks;
}
function parseQualifiedId(qualifiedId) {
    if (qualifiedId === undefined) {
        return null;
    }
    const separatorIndex = qualifiedId.indexOf("/");
    if (separatorIndex <= 0 || separatorIndex === qualifiedId.length - 1) {
        return null;
    }
    return {
        list: qualifiedId.slice(0, separatorIndex),
        id: qualifiedId.slice(separatorIndex + 1)
    };
}
function stateNamesBy(states, predicate) {
    return Object.entries(states)
        .filter(([, state]) => predicate(state))
        .map(([name]) => name);
}
function withoutAgent(overrides) {
    const { agent: ignoredAgent, maxConcurrentAgents: ignoredMaxConcurrentAgents, ...rest } = overrides;
    return rest;
}
function withoutEvents(overrides) {
    const { events: ignoredEvents, ...rest } = overrides;
    return rest;
}
