import path from "node:path";
import { appendFile, readFile } from "node:fs/promises";
import { createId, createRunId, ensureDir, nowIso, readJsonFile, sanitizeJson, sanitizeText, withFileLock, writeJsonFile, writeTextFile } from "../../shared/index.js";
import { LAB_REPRESENTATIVE_AGENT } from "../../agents/identity.js";
import { SEED_ACTIVE_RUN_SCHEMA_VERSION, SEED_RUN_SCHEMA_VERSION, SEED_TASK_SCHEMA_VERSION } from "./types.js";
import { appendEvent, appendToolCall } from "../observability/index.js";
import { initializeRunBudget } from "../run-budget/store.js";
import { mutateRevisionedJsonFile } from "../state-store/index.js";
export function createRunStore(directory, config) {
    return { directory, config };
}
export function artifactRoot(store) {
    return path.resolve(store.directory, store.config.trace.artifact_root);
}
export function runDir(store, runId) {
    return path.join(artifactRoot(store), runId);
}
export function activeRunPath(store) {
    return path.join(path.resolve(store.directory, ".seed-agent"), "active-run.json");
}
export async function createRun(store, input) {
    const run_id = input.runId ?? createRunId();
    const dir = runDir(store, run_id);
    const timestamp = nowIso();
    await ensureDir(path.join(dir, "artifacts"));
    const run = {
        schema_version: SEED_RUN_SCHEMA_VERSION,
        state_revision: 1,
        run_id,
        created_at: timestamp,
        updated_at: timestamp,
        root_session_id: input.sessionId,
        professor_goal: input.goal,
        status: "running",
        run_mode: input.runMode ?? "single_agent",
        active_agent: store.config.default_run_agent,
        model_provider: input.modelProvider,
        model_id: input.modelId,
        model_variant: input.modelVariant,
        plugin_version: "0.1.0",
        workspace_root: store.directory,
        acceptance_criteria: input.acceptanceCriteria ?? ["Professor goal is completed or a real blocker is reported."],
        final_artifacts: [path.join(dir, "final_report.md")],
    };
    await writeJsonFile(path.join(dir, "run.json"), run);
    await writeJsonFile(path.join(dir, "tasks.json"), []);
    await writeTextFile(path.join(dir, "agent_calls.jsonl"), "");
    await writeTextFile(path.join(dir, "model_calls.jsonl"), "");
    await writeTextFile(path.join(dir, "tool_calls.jsonl"), "");
    await writeTextFile(path.join(dir, "events.jsonl"), "");
    await writeJsonFile(path.join(dir, "background_tasks.json"), []);
    await writeTextFile(path.join(dir, "evidence.md"), "# Evidence\n\n");
    await writeTextFile(path.join(dir, "progress.md"), `# Progress\n\nStatus: running\n\nGoal: ${sanitizeText(input.goal)}\n`);
    await writeTextFile(path.join(dir, "final_report.md"), `# Final Report\n\nRun ${run_id} is in progress.\n`);
    await initializeRunBudget(store, run_id, timestamp);
    await setActiveRun(store, run_id);
    await appendEvent(store, run_id, {
        type: "run_created",
        actor: store.config.default_run_agent,
        summary: "SEED run created",
        session_id: input.sessionId,
        metadata: {
            goal: input.goal,
            selected_model: input.modelProvider && input.modelId
                ? {
                    providerID: input.modelProvider,
                    modelID: input.modelId,
                    variant: input.modelVariant,
                }
                : undefined,
        },
    });
    return run;
}
export async function setActiveRun(store, runId) {
    await mutateRevisionedJsonFile(activeRunPath(store), () => ({
        schema_version: SEED_ACTIVE_RUN_SCHEMA_VERSION,
        state_revision: 0,
        run_id: runId,
        run_dir: runDir(store, runId),
        updated_at: nowIso(),
    }));
}
export async function getActiveRun(store) {
    const file = activeRunPath(store);
    const raw = await readJsonFile(file);
    if (!raw)
        return undefined;
    const migrated = normalizeActiveRun(store, raw);
    if (JSON.stringify(raw) !== JSON.stringify(migrated))
        await writeJsonFile(file, migrated);
    return migrated;
}
export async function getRun(store, runId) {
    const active = runId ? undefined : await getActiveRun(store);
    const id = runId ?? active?.run_id;
    if (!id)
        return undefined;
    const file = path.join(runDir(store, id), "run.json");
    const raw = await readJsonFile(file);
    if (!raw)
        return undefined;
    const migrated = normalizeRun(store, id, raw);
    if (JSON.stringify(raw) !== JSON.stringify(migrated))
        await writeJsonFile(file, migrated);
    return migrated;
}
export async function updateRun(store, runId, patch) {
    const file = path.join(runDir(store, runId), "run.json");
    return mutateRevisionedJsonFile(file, (raw) => {
        if (!raw)
            throw new Error(`SEED run not found: ${runId}`);
        const current = normalizeRun(store, runId, raw);
        return { ...current, ...patch };
    });
}
export async function listTasks(store, runId) {
    const file = path.join(runDir(store, runId), "tasks.json");
    const raw = (await readJsonFile(file)) ?? [];
    const migrated = normalizeTasks(raw);
    if (JSON.stringify(raw) !== JSON.stringify(migrated))
        await writeJsonFile(file, migrated);
    return migrated;
}
export async function getTask(store, runId, taskId) {
    return (await listTasks(store, runId)).find((task) => task.task_id === taskId);
}
export async function createTask(store, runId, input) {
    const tasksFile = path.join(runDir(store, runId), "tasks.json");
    const task = await withFileLock(tasksFile, async () => {
        const tasks = normalizeTasks((await readJsonFile(tasksFile)) ?? []);
        const timestamp = nowIso();
        const nextTask = {
            schema_version: SEED_TASK_SCHEMA_VERSION,
            task_id: createId("task"),
            title: input.title,
            description: input.description ?? "",
            owner_agent: input.owner_agent ?? store.config.default_run_agent,
            status: "pending",
            priority: input.priority ?? 3,
            depends_on: input.depends_on ?? [],
            acceptance_criteria: input.acceptance_criteria ?? [],
            evidence_refs: [],
            created_at: timestamp,
            updated_at: timestamp,
        };
        tasks.push(nextTask);
        await writeJsonFile(tasksFile, tasks);
        return nextTask;
    });
    await appendEvent(store, runId, {
        type: "task_created",
        actor: task.owner_agent,
        summary: task.title,
        related_task_id: task.task_id,
    });
    return task;
}
export async function updateTask(store, runId, taskId, patch) {
    const tasksFile = path.join(runDir(store, runId), "tasks.json");
    const task = await withFileLock(tasksFile, async () => {
        const tasks = normalizeTasks((await readJsonFile(tasksFile)) ?? []);
        const index = tasks.findIndex((item) => item.task_id === taskId);
        if (index < 0)
            throw new Error(`SEED task not found: ${taskId}`);
        const completed_at = patch.status === "completed" ? nowIso() : patch.completed_at;
        const current = tasks[index];
        const candidate = { ...current, ...patch };
        const blockers = activeTaskTransitionStatuses.has(candidate.status) ? incompleteTaskDependencies(candidate, tasks) : [];
        if (blockers.length)
            throw new Error(`SEED task blocked by incomplete dependencies: ${blockers.join(",")}`);
        const nextTask = { ...candidate, updated_at: nowIso(), ...(completed_at ? { completed_at } : {}) };
        tasks[index] = nextTask;
        await writeJsonFile(tasksFile, tasks);
        return nextTask;
    });
    await appendEvent(store, runId, {
        type: "task_status_changed",
        actor: task.owner_agent,
        summary: `${task.title}: ${task.status}`,
        related_task_id: task.task_id,
    });
    return task;
}
export async function completePrimaryTasks(store, runId, evidenceRefs = []) {
    const tasksFile = path.join(runDir(store, runId), "tasks.json");
    const completed = await withFileLock(tasksFile, async () => {
        const tasks = normalizeTasks((await readJsonFile(tasksFile)) ?? []);
        const nextCompleted = [];
        const timestamp = nowIso();
        const next = tasks.map((task) => {
            if (task.title !== "Complete professor goal")
                return task;
            if (task.status === "completed" || task.status === "cancelled")
                return task;
            const blockers = incompleteTaskDependencies(task, tasks);
            if (blockers.length)
                throw new Error(`SEED task blocked by incomplete dependencies: ${blockers.join(",")}`);
            const updated = {
                ...task,
                status: "completed",
                blocker: "",
                evidence_refs: unique([...task.evidence_refs, ...evidenceRefs]),
                updated_at: timestamp,
                completed_at: timestamp,
            };
            nextCompleted.push(updated);
            return updated;
        });
        if (nextCompleted.length)
            await writeJsonFile(tasksFile, next);
        return nextCompleted;
    });
    if (!completed.length)
        return [];
    for (const task of completed) {
        await appendEvent(store, runId, {
            type: "task_status_changed",
            actor: task.owner_agent,
            summary: `${task.title}: ${task.status}`,
            related_task_id: task.task_id,
        });
    }
    return completed;
}
const activeTaskTransitionStatuses = new Set(["in_progress", "review", "completed"]);
function incompleteTaskDependencies(task, tasks) {
    return task.depends_on.filter((id) => tasks.find((candidate) => candidate.task_id === id)?.status !== "completed");
}
export async function writeProgress(store, runId, content, append = false) {
    const file = path.join(runDir(store, runId), "progress.md");
    await withFileLock(file, async () => {
        if (append) {
            await appendFile(file, `\n${sanitizeText(content)}\n`, "utf8");
        }
        else {
            await writeTextFile(file, sanitizeText(content));
        }
    });
    await appendEvent(store, runId, { type: "progress_written", actor: LAB_REPRESENTATIVE_AGENT, summary: "Progress note updated" });
    return file;
}
function unique(values) {
    return [...new Set(values.filter(Boolean))];
}
function normalizeActiveRun(store, raw) {
    const runId = stringValue(raw.run_id) ?? "";
    return {
        ...raw,
        schema_version: SEED_ACTIVE_RUN_SCHEMA_VERSION,
        state_revision: numberValue(raw.state_revision) ?? 1,
        run_id: runId,
        run_dir: stringValue(raw.run_dir) ?? runDir(store, runId),
        updated_at: stringValue(raw.updated_at) ?? nowIso(),
    };
}
function normalizeRun(store, runId, raw) {
    const id = stringValue(raw.run_id) ?? runId;
    const timestamp = nowIso();
    return {
        ...raw,
        schema_version: SEED_RUN_SCHEMA_VERSION,
        state_revision: numberValue(raw.state_revision) ?? 1,
        run_id: id,
        created_at: stringValue(raw.created_at) ?? timestamp,
        updated_at: stringValue(raw.updated_at) ?? timestamp,
        root_session_id: stringValue(raw.root_session_id),
        professor_goal: stringValue(raw.professor_goal) ?? "Ad hoc SEED tool run",
        status: isRunStatus(raw.status) ? raw.status : "running",
        run_mode: raw.run_mode === "coordinator" ? "coordinator" : "single_agent",
        coordinator_direct_work_reason: stringValue(raw.coordinator_direct_work_reason),
        coordinator_direct_work_granted_at: stringValue(raw.coordinator_direct_work_granted_at),
        coordinator_direct_work_used_at: stringValue(raw.coordinator_direct_work_used_at),
        active_agent: stringValue(raw.active_agent) ?? store.config.default_run_agent,
        model_provider: stringValue(raw.model_provider),
        model_id: stringValue(raw.model_id),
        model_variant: stringValue(raw.model_variant),
        plugin_version: stringValue(raw.plugin_version) ?? "0.1.0",
        workspace_root: stringValue(raw.workspace_root) ?? store.directory,
        acceptance_criteria: stringArray(raw.acceptance_criteria, ["Professor goal is completed or a real blocker is reported."]),
        final_artifacts: stringArray(raw.final_artifacts, [path.join(runDir(store, id), "final_report.md")]),
        completed_at: stringValue(raw.completed_at),
    };
}
function normalizeTasks(rawTasks) {
    return rawTasks.map(normalizeTask);
}
function normalizeTask(raw) {
    const timestamp = nowIso();
    return {
        ...raw,
        schema_version: SEED_TASK_SCHEMA_VERSION,
        task_id: stringValue(raw.task_id) ?? createId("task"),
        title: stringValue(raw.title) ?? "Untitled task",
        description: stringValue(raw.description) ?? "",
        owner_agent: stringValue(raw.owner_agent) ?? LAB_REPRESENTATIVE_AGENT,
        status: isTaskStatus(raw.status) ? raw.status : "pending",
        priority: numberValue(raw.priority) ?? 3,
        depends_on: stringArray(raw.depends_on),
        acceptance_criteria: stringArray(raw.acceptance_criteria),
        evidence_refs: stringArray(raw.evidence_refs),
        created_at: stringValue(raw.created_at) ?? timestamp,
        updated_at: stringValue(raw.updated_at) ?? timestamp,
        completed_at: stringValue(raw.completed_at),
        blocker: stringValue(raw.blocker),
    };
}
function stringArray(value, fallback = []) {
    return Array.isArray(value) ? value.filter((item) => typeof item === "string") : fallback;
}
function stringValue(value) {
    return typeof value === "string" && value.length > 0 ? value : undefined;
}
function numberValue(value) {
    return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
function isRunStatus(value) {
    return typeof value === "string" && ["created", "clarifying", "running", "blocked", "failed", "completed", "stopped", "awaiting_feedback"].includes(value);
}
function isTaskStatus(value) {
    return typeof value === "string" && ["pending", "in_progress", "blocked", "review", "completed", "cancelled"].includes(value);
}
export async function writeReport(store, runId, content) {
    const file = path.join(runDir(store, runId), "final_report.md");
    await writeTextFile(file, sanitizeText(content));
    await updateRun(store, runId, { final_artifacts: [file] });
    await appendEvent(store, runId, { type: "report_written", actor: LAB_REPRESENTATIVE_AGENT, summary: "Final report updated" });
    return file;
}
export async function readRunTextFile(store, runId, name) {
    return readFile(path.join(runDir(store, runId), name), "utf8");
}
export async function recordToolStart(store, runId, input) {
    const toolCallId = createId("tool");
    const args = store.config.trace.sanitize_outputs ? sanitizeJson(input.args) : input.args;
    await appendToolCall(store, runId, {
        tool_call_id: toolCallId,
        timestamp_start: nowIso(),
        run_id: runId,
        session_id: input.sessionId,
        agent: input.agent,
        tool_name: input.toolName,
        args_summary: args === undefined ? undefined : JSON.stringify(args).slice(0, 500),
        status: "started",
        evidence_refs: [],
    });
    return toolCallId;
}
//# sourceMappingURL=store.js.map