import { createId, nowIso, readJsonFile, sanitizeText, withFileLock, writeJsonFile } from "../../shared/index.js";
import { LAB_REPRESENTATIVE_AGENT } from "../../agents/identity.js";
import { appendEvent } from "../observability/index.js";
import { assertTeamMember, ensureTeamRuntime, teamTasksPath, updateTeamMemberStatus } from "./store.js";
export async function listTeamTasks(store, runId) {
    return ((await readJsonFile(teamTasksPath(store, runId))) ?? []).map(normalizeTeamTask);
}
export async function createTeamTask(store, runId, input) {
    await ensureTeamRuntime(store, runId);
    const file = teamTasksPath(store, runId);
    const task = await withFileLock(file, async () => {
        const tasks = ((await readJsonFile(file)) ?? []).map(normalizeTeamTask);
        const timestamp = nowIso();
        const next = {
            team_task_id: createId("team_task"),
            title: sanitizeText(input.title),
            description: sanitizeText(input.description ?? ""),
            status: "pending",
            owner_agent: null,
            priority: input.priority ?? 3,
            revision: 1,
            depends_on: input.depends_on ?? [],
            evidence_refs: [],
            conflicts: [],
            created_at: timestamp,
            updated_at: timestamp,
        };
        tasks.push(next);
        await writeJsonFile(file, tasks);
        return next;
    });
    await appendEvent(store, runId, {
        type: "team_task_created",
        actor: store.config.default_run_agent ?? LAB_REPRESENTATIVE_AGENT,
        summary: task.title,
        metadata: { team_task_id: task.team_task_id },
    });
    return task;
}
function incompleteBlockers(task, tasks) {
    return task.depends_on.filter((id) => tasks.find((candidate) => candidate.team_task_id === id)?.status !== "completed");
}
const activeTaskStatuses = new Set(["claimed", "in_progress", "review"]);
const terminalTaskStatuses = new Set(["completed", "cancelled"]);
function memberStatusFromTasks(member, tasks) {
    const owned = tasks.filter((task) => task.owner_agent === member && !terminalTaskStatuses.has(task.status));
    if (owned.some((task) => task.status === "blocked"))
        return "blocked";
    if (owned.some((task) => activeTaskStatuses.has(task.status)))
        return "active";
    return "idle";
}
async function refreshMemberStatuses(store, runId, members, tasks) {
    const uniqueMembers = Array.from(new Set(members.filter(Boolean)));
    for (const member of uniqueMembers) {
        await updateTeamMemberStatus(store, runId, member, memberStatusFromTasks(member, tasks));
    }
}
export async function claimTeamTask(store, runId, teamTaskId, member) {
    await ensureTeamRuntime(store, runId);
    await assertTeamMember(store, runId, member);
    const file = teamTasksPath(store, runId);
    const result = await withFileLock(file, async () => {
        const tasks = ((await readJsonFile(file)) ?? []).map(normalizeTeamTask);
        const index = tasks.findIndex((candidate) => candidate.team_task_id === teamTaskId);
        if (index < 0)
            throw new Error(`team task not found: ${teamTaskId}`);
        const current = tasks[index];
        if (!current)
            throw new Error(`team task not found: ${teamTaskId}`);
        if (current.status !== "pending")
            throw new Error(`team task is not claimable: ${current.status}`);
        const blockers = incompleteBlockers(current, tasks);
        if (blockers.length)
            throw new Error(`team task blocked by ${blockers.join(",")}`);
        const timestamp = nowIso();
        const next = {
            ...current,
            status: "claimed",
            owner_agent: member,
            revision: taskRevision(current) + 1,
            conflicts: current.conflicts ?? [],
            claimed_at: timestamp,
            updated_at: timestamp,
        };
        tasks[index] = next;
        await writeJsonFile(file, tasks);
        return { task: next, tasks };
    });
    const task = result.task;
    await refreshMemberStatuses(store, runId, [member], result.tasks);
    await appendEvent(store, runId, {
        type: "team_task_claimed",
        actor: member,
        summary: task.title,
        metadata: { team_task_id: task.team_task_id },
    });
    return task;
}
export async function updateTeamTask(store, runId, teamTaskId, patch) {
    await ensureTeamRuntime(store, runId);
    if (patch.owner_agent)
        await assertTeamMember(store, runId, patch.owner_agent);
    const file = teamTasksPath(store, runId);
    const result = await withFileLock(file, async () => {
        const tasks = ((await readJsonFile(file)) ?? []).map(normalizeTeamTask);
        const index = tasks.findIndex((candidate) => candidate.team_task_id === teamTaskId);
        if (index < 0)
            throw new Error(`team task not found: ${teamTaskId}`);
        const current = tasks[index];
        if (!current)
            throw new Error(`team task not found: ${teamTaskId}`);
        const timestamp = nowIso();
        const currentRevision = taskRevision(current);
        if (patch.expected_revision !== undefined && patch.expected_revision !== currentRevision) {
            const conflict = createStaleRevisionConflict({
                current,
                patch,
                actualRevision: currentRevision,
                timestamp,
            });
            const next = {
                ...current,
                status: "blocked",
                revision: currentRevision + 1,
                conflicts: [...(current.conflicts ?? []), conflict],
                blocker: conflict.summary,
                updated_at: timestamp,
            };
            tasks[index] = next;
            await writeJsonFile(file, tasks);
            return {
                task: next,
                tasks,
                affectedMembers: [current.owner_agent].filter((member) => Boolean(member)),
                conflict,
            };
        }
        const status = patch.status ?? current.status;
        const next = {
            ...current,
            title: patch.title !== undefined ? sanitizeText(patch.title) : current.title,
            description: patch.description !== undefined ? sanitizeText(patch.description) : current.description,
            status,
            owner_agent: patch.owner_agent === undefined ? current.owner_agent : patch.owner_agent,
            evidence_refs: patch.evidence_refs ?? current.evidence_refs,
            revision: currentRevision + 1,
            conflicts: current.conflicts ?? [],
            blocker: patch.blocker === undefined ? current.blocker : sanitizeText(patch.blocker),
            updated_at: timestamp,
            completed_at: status === "completed" ? timestamp : current.completed_at,
        };
        tasks[index] = next;
        await writeJsonFile(file, tasks);
        return { task: next, tasks, affectedMembers: [current.owner_agent, next.owner_agent].filter((member) => Boolean(member)) };
    });
    const task = result.task;
    await refreshMemberStatuses(store, runId, result.affectedMembers, result.tasks);
    if (result.conflict) {
        await appendEvent(store, runId, {
            type: "team_task_conflict_recorded",
            actor: result.conflict.actor,
            summary: result.conflict.summary,
            metadata: { team_task_id: task.team_task_id, conflict_id: result.conflict.conflict_id },
        });
    }
    await appendEvent(store, runId, {
        type: result.conflict ? "team_task_update_blocked_by_conflict" : "team_task_updated",
        actor: task.owner_agent ?? store.config.default_run_agent ?? LAB_REPRESENTATIVE_AGENT,
        summary: `${task.title}: ${task.status}`,
        metadata: { team_task_id: task.team_task_id },
    });
    return task;
}
export async function releaseTeamTask(store, runId, teamTaskId, member) {
    await ensureTeamRuntime(store, runId);
    if (member)
        await assertTeamMember(store, runId, member);
    const file = teamTasksPath(store, runId);
    const result = await withFileLock(file, async () => {
        const tasks = ((await readJsonFile(file)) ?? []).map(normalizeTeamTask);
        const index = tasks.findIndex((candidate) => candidate.team_task_id === teamTaskId);
        if (index < 0)
            throw new Error(`team task not found: ${teamTaskId}`);
        const current = tasks[index];
        if (!current)
            throw new Error(`team task not found: ${teamTaskId}`);
        if (terminalTaskStatuses.has(current.status))
            throw new Error(`team task is not releasable: ${current.status}`);
        if (member && current.owner_agent !== member)
            throw new Error(`team task is owned by ${current.owner_agent ?? "unclaimed"}`);
        const timestamp = nowIso();
        const { claimed_at, completed_at, blocker, ...rest } = current;
        const next = {
            ...rest,
            status: "pending",
            owner_agent: null,
            revision: taskRevision(current) + 1,
            conflicts: current.conflicts ?? [],
            updated_at: timestamp,
        };
        tasks[index] = next;
        await writeJsonFile(file, tasks);
        return { task: next, tasks, affectedMembers: current.owner_agent ? [current.owner_agent] : [] };
    });
    await refreshMemberStatuses(store, runId, result.affectedMembers, result.tasks);
    await appendEvent(store, runId, {
        type: "team_task_released",
        actor: member ?? store.config.default_run_agent ?? LAB_REPRESENTATIVE_AGENT,
        summary: result.task.title,
        metadata: { team_task_id: result.task.team_task_id },
    });
    return result.task;
}
export function unresolvedTeamTaskConflicts(tasks) {
    return tasks.flatMap((task) => (task.conflicts ?? []).filter((conflict) => !conflict.resolved_at));
}
function taskRevision(task) {
    return Number.isInteger(task.revision) && task.revision > 0 ? task.revision : 1;
}
function normalizeTeamTask(task) {
    return {
        ...task,
        revision: taskRevision(task),
        conflicts: task.conflicts ?? [],
    };
}
function createStaleRevisionConflict(input) {
    const actor = sanitizeText(input.patch.actor ?? "unknown-agent");
    const expected = input.patch.expected_revision ?? 0;
    return {
        conflict_id: createId("team_conflict"),
        kind: "stale_revision",
        actor,
        expected_revision: expected,
        actual_revision: input.actualRevision,
        attempted_patch: sanitizeConflictPatch(input.patch),
        summary: `Stale team task update from ${actor}: expected revision ${expected}, actual revision ${input.actualRevision}.`,
        created_at: input.timestamp,
    };
}
function sanitizeConflictPatch(patch) {
    const result = {};
    if (patch.title !== undefined)
        result.title = sanitizeText(patch.title);
    if (patch.description !== undefined)
        result.description = sanitizeText(patch.description);
    if (patch.status !== undefined)
        result.status = patch.status;
    if (patch.owner_agent !== undefined)
        result.owner_agent = patch.owner_agent;
    if (patch.evidence_refs !== undefined)
        result.evidence_refs = patch.evidence_refs.map((ref) => sanitizeText(ref));
    if (patch.blocker !== undefined)
        result.blocker = sanitizeText(patch.blocker);
    return result;
}
//# sourceMappingURL=task-board.js.map