"use strict";
// Structured task store.
//
// JSON-persisted task lifecycle: pending → assigned → in_progress → blocked
// → review → done | failed | cancelled.
//
// Designed so sub-agents can self-report progress (task_progress tool) and
// the main agent / heartbeat can monitor stale tasks.
//
// Storage: .harness/tasks/tasks.json
//
// All mutations emit events via emitEvent so the WebSocket broadcaster
// notifies live UI clients.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.listTasks = listTasks;
exports.getTask = getTask;
exports.createTask = createTask;
exports.updateTask = updateTask;
exports.recordCheckIn = recordCheckIn;
exports.deleteTask = deleteTask;
exports.detectStaleTasks = detectStaleTasks;
exports.summarizeTasks = summarizeTasks;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const eventStore_1 = require("../persistence/eventStore");
const STALE_CHECK_IN_MS = 30 * 60 * 1000;
function tasksFilePath(projectDir) {
    return path.join(projectDir, '.harness', 'tasks', 'tasks.json');
}
async function readAll(projectDir) {
    try {
        const raw = await fs.readFile(tasksFilePath(projectDir), 'utf-8');
        const parsed = JSON.parse(raw);
        return Array.isArray(parsed.tasks) ? parsed.tasks : [];
    }
    catch {
        return [];
    }
}
async function writeAll(projectDir, tasks) {
    const fp = tasksFilePath(projectDir);
    await fs.mkdir(path.dirname(fp), { recursive: true });
    await fs.writeFile(fp, JSON.stringify({ tasks }, null, 2), 'utf-8');
}
async function listTasks(projectDir, filter = {}) {
    const tasks = await readAll(projectDir);
    return tasks.filter((task) => {
        if (filter.status && task.status !== filter.status)
            return false;
        if (filter.assigneeId && task.assigneeId !== filter.assigneeId)
            return false;
        if (filter.companyId && task.companyId !== filter.companyId)
            return false;
        if (filter.goalId && task.goalId !== filter.goalId)
            return false;
        return true;
    });
}
async function getTask(projectDir, id) {
    const tasks = await readAll(projectDir);
    return tasks.find((task) => task.id === id);
}
async function createTask(projectDir, input, now = new Date()) {
    if (!input.title || !input.title.trim())
        throw new Error('Task title is required');
    const task = {
        id: crypto.randomUUID(),
        title: input.title.trim(),
        description: input.description?.trim() || undefined,
        status: input.assigneeId ? 'assigned' : 'pending',
        priority: input.priority ?? 'normal',
        createdAt: now.toISOString(),
        updatedAt: now.toISOString(),
        assigneeId: input.assigneeId,
        parentTaskId: input.parentTaskId,
        dependsOn: input.dependsOn ?? [],
        progressPercent: 0,
        checkIns: [],
        tags: input.tags ?? [],
        metadata: input.metadata,
        companyId: input.companyId,
        goalId: input.goalId,
        checkedOutBy: undefined,
        checkedOutAt: undefined,
        executionPolicy: input.executionPolicy,
        approvalState: input.executionPolicy === 'auto' || !input.executionPolicy ? 'not_required' : 'pending',
    };
    const tasks = await readAll(projectDir);
    tasks.push(task);
    await writeAll(projectDir, tasks);
    await (0, eventStore_1.emitEvent)(projectDir, 'task', 'task.created', { task }, 'system', task.id).catch(() => { });
    return task;
}
async function updateTask(projectDir, id, input, now = new Date()) {
    const tasks = await readAll(projectDir);
    const idx = tasks.findIndex((task) => task.id === id);
    if (idx === -1)
        throw new Error(`Task not found: ${id}`);
    const previous = tasks[idx];
    const updated = {
        ...previous,
        ...(input.title !== undefined ? { title: input.title.trim() } : {}),
        ...(input.description !== undefined ? { description: input.description.trim() || undefined } : {}),
        ...(input.status !== undefined ? { status: input.status } : {}),
        ...(input.priority !== undefined ? { priority: input.priority } : {}),
        ...(input.assigneeId !== undefined ? { assigneeId: input.assigneeId || undefined } : {}),
        ...(input.dependsOn !== undefined ? { dependsOn: input.dependsOn } : {}),
        ...(input.tags !== undefined ? { tags: input.tags } : {}),
        ...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
        ...(input.companyId !== undefined ? { companyId: input.companyId || undefined } : {}),
        ...(input.goalId !== undefined ? { goalId: input.goalId || undefined } : {}),
        ...(input.checkedOutBy !== undefined ? { checkedOutBy: input.checkedOutBy || undefined } : {}),
        ...(input.checkedOutAt !== undefined ? { checkedOutAt: input.checkedOutAt || undefined } : {}),
        ...(input.executionPolicy !== undefined ? { executionPolicy: input.executionPolicy } : {}),
        ...(input.approvalState !== undefined ? { approvalState: input.approvalState } : {}),
        updatedAt: now.toISOString(),
    };
    // Auto-assign status when assignee changes from undefined → defined.
    if (!previous.assigneeId && updated.assigneeId && updated.status === 'pending') {
        updated.status = 'assigned';
    }
    // Guard: block transition to in_progress if approval is required and not yet approved.
    if (updated.status === 'in_progress' && updated.executionPolicy && updated.executionPolicy !== 'auto') {
        if (updated.approvalState !== 'approved') {
            throw new Error(`Task ${id} requires approval before starting (executionPolicy=${updated.executionPolicy}, approvalState=${updated.approvalState ?? 'pending'})`);
        }
    }
    tasks[idx] = updated;
    await writeAll(projectDir, tasks);
    await (0, eventStore_1.emitEvent)(projectDir, 'task', 'task.updated', { task: updated, previous }, 'system', updated.id).catch(() => { });
    return updated;
}
async function recordCheckIn(projectDir, id, checkIn, now = new Date()) {
    const tasks = await readAll(projectDir);
    const idx = tasks.findIndex((task) => task.id === id);
    if (idx === -1)
        throw new Error(`Task not found: ${id}`);
    const task = tasks[idx];
    const entry = {
        timestamp: now.toISOString(),
        progressPercent: typeof checkIn.progressPercent === 'number'
            ? Math.max(0, Math.min(100, Math.round(checkIn.progressPercent)))
            : undefined,
        message: checkIn.message,
        status: checkIn.status,
    };
    task.checkIns.push(entry);
    if (entry.progressPercent !== undefined)
        task.progressPercent = entry.progressPercent;
    if (entry.status)
        task.status = entry.status;
    // Implicit transitions: any check-in advances pending/assigned to in_progress.
    if (task.status === 'pending' || task.status === 'assigned')
        task.status = 'in_progress';
    task.updatedAt = now.toISOString();
    tasks[idx] = task;
    await writeAll(projectDir, tasks);
    await (0, eventStore_1.emitEvent)(projectDir, 'task', 'task.check_in', { taskId: id, checkIn: entry, task }, 'system', id).catch(() => { });
    return task;
}
async function deleteTask(projectDir, id) {
    const tasks = await readAll(projectDir);
    const idx = tasks.findIndex((task) => task.id === id);
    if (idx === -1)
        return false;
    const [removed] = tasks.splice(idx, 1);
    await writeAll(projectDir, tasks);
    await (0, eventStore_1.emitEvent)(projectDir, 'task', 'task.deleted', { task: removed }, 'system', id).catch(() => { });
    return true;
}
/** Identify in-progress tasks that have not checked in within STALE_CHECK_IN_MS. */
async function detectStaleTasks(projectDir, now = new Date(), staleMs = STALE_CHECK_IN_MS) {
    const tasks = await readAll(projectDir);
    const reports = [];
    for (const task of tasks) {
        if (task.status !== 'in_progress')
            continue;
        const lastCheckIn = task.checkIns[task.checkIns.length - 1];
        const lastActivityAt = lastCheckIn?.timestamp ?? task.updatedAt;
        const elapsed = now.getTime() - Date.parse(lastActivityAt);
        if (elapsed > staleMs) {
            reports.push({ taskId: task.id, title: task.title, assigneeId: task.assigneeId, lastActivityAt, staleForMs: elapsed });
        }
    }
    return reports;
}
/** Summarize tasks for board-style UIs (tasks grouped by status with counts). */
async function summarizeTasks(projectDir) {
    const tasks = await readAll(projectDir);
    const summary = {
        pending: 0,
        assigned: 0,
        in_progress: 0,
        blocked: 0,
        review: 0,
        done: 0,
        failed: 0,
        cancelled: 0,
    };
    for (const task of tasks)
        summary[task.status] += 1;
    return { ...summary, total: tasks.length };
}
//# sourceMappingURL=taskStore.js.map