import path from "node:path";
import { appendJsonl, createId, ensureDir, nowIso, readJsonFile, readJsonl, sanitizeText, withFileLock, writeJsonFile } from "../../shared/index.js";
import { runDir } from "../run-state/index.js";
export function agentThreadsRoot(store, runId) {
    return path.join(runDir(store, runId), "threads");
}
export function agentThreadDir(store, runId, threadId) {
    return path.join(agentThreadsRoot(store, runId), safeThreadId(threadId));
}
export function agentThreadsPath(store, runId) {
    return path.join(agentThreadsRoot(store, runId), "threads.json");
}
export function agentThreadHelpersPath(store, runId, threadId) {
    return path.join(agentThreadDir(store, runId, threadId), "helpers.jsonl");
}
export function agentThreadToolManifestPath(store, runId, threadId) {
    return path.join(agentThreadDir(store, runId, threadId), "tool-manifest.json");
}
export async function listAgentThreads(store, runId) {
    const raw = (await readJsonFile(agentThreadsPath(store, runId))) ?? [];
    return raw
        .map((thread) => normalizeThread(runId, thread))
        .filter((thread) => thread !== undefined)
        .sort((left, right) => left.created_at.localeCompare(right.created_at) || left.thread_id.localeCompare(right.thread_id));
}
export async function getAgentThread(store, runId, threadId) {
    const normalized = safeThreadId(threadId);
    return (await listAgentThreads(store, runId)).find((thread) => thread.thread_id === normalized);
}
export async function findAgentThreadBySession(store, runId, sessionId) {
    if (!sessionId)
        return undefined;
    return (await listAgentThreads(store, runId)).find((thread) => thread.opencode_session_id === sessionId);
}
export async function upsertAgentThread(store, runId, input) {
    const file = agentThreadsPath(store, runId);
    const threadId = safeThreadId(input.thread_id);
    const timestamp = nowIso();
    const nextThread = await withFileLock(file, async () => {
        const threads = (await readJsonFile(file)) ?? [];
        const normalized = threads
            .map((thread) => normalizeThread(runId, thread))
            .filter((thread) => thread !== undefined);
        const index = normalized.findIndex((thread) => thread.thread_id === threadId);
        const current = index >= 0 ? normalized[index] : undefined;
        const next = normalizeThread(runId, {
            ...current,
            ...input,
            thread_id: threadId,
            run_id: runId,
            created_at: input.created_at ?? current?.created_at ?? timestamp,
            updated_at: timestamp,
        });
        if (!next)
            throw new Error("agent thread input did not include a usable thread_id");
        if (index >= 0)
            normalized[index] = next;
        else
            normalized.push(next);
        await writeJsonFile(file, normalized);
        return next;
    });
    await ensureDir(agentThreadDir(store, runId, threadId));
    return nextThread;
}
export async function patchAgentThread(store, runId, threadId, patch) {
    const file = agentThreadsPath(store, runId);
    const normalizedId = safeThreadId(threadId);
    const timestamp = nowIso();
    return withFileLock(file, async () => {
        const threads = (await readJsonFile(file)) ?? [];
        const normalized = threads
            .map((thread) => normalizeThread(runId, thread))
            .filter((thread) => thread !== undefined);
        const index = normalized.findIndex((thread) => thread.thread_id === normalizedId);
        if (index < 0)
            return undefined;
        const current = normalized[index];
        const cleanPatch = withoutUndefined(patch);
        const status = patch.status ?? current.status;
        const next = {
            ...current,
            ...cleanPatch,
            status,
            updated_at: timestamp,
            completed_at: isTerminalStatus(status) ? (patch.completed_at ?? current.completed_at ?? timestamp) : patch.completed_at,
        };
        normalized[index] = next;
        await writeJsonFile(file, normalized);
        return next;
    });
}
export async function writeAgentThreadToolManifest(store, runId, threadId, input) {
    const normalizedId = safeThreadId(threadId);
    const ref = `threads/${normalizedId}/tool-manifest.json`;
    const manifest = {
        version: 1,
        run_id: runId,
        thread_id: normalizedId,
        updated_at: nowIso(),
        ...input,
    };
    await ensureDir(agentThreadDir(store, runId, normalizedId));
    await writeJsonFile(agentThreadToolManifestPath(store, runId, normalizedId), manifest);
    await patchAgentThread(store, runId, normalizedId, { tool_manifest_ref: ref });
    return ref;
}
export async function listAgentThreadHelpers(store, runId, ownerThreadId) {
    const raw = await readJsonl(agentThreadHelpersPath(store, runId, ownerThreadId));
    const latest = new Map();
    for (const entry of raw) {
        const helper = normalizeHelperCall(runId, ownerThreadId, entry);
        if (helper)
            latest.set(helper.helper_id, helper);
    }
    return [...latest.values()].sort((left, right) => left.created_at.localeCompare(right.created_at) || left.helper_id.localeCompare(right.helper_id));
}
export async function findAgentThreadHelperByBackground(store, runId, backgroundId) {
    if (!backgroundId)
        return undefined;
    for (const thread of await listAgentThreads(store, runId)) {
        const found = (await listAgentThreadHelpers(store, runId, thread.thread_id)).find((helper) => helper.background_id === backgroundId);
        if (found)
            return found;
    }
    return undefined;
}
export async function recordHelperLaunch(store, runId, input) {
    const ownerThreadId = safeThreadId(input.owner_thread_id);
    if (!ownerThreadId)
        throw new Error("helper call requires owner_thread_id");
    const timestamp = nowIso();
    const helper = normalizeHelperCall(runId, ownerThreadId, {
        ...input,
        helper_id: input.helper_id ?? createId("helper"),
        run_id: runId,
        status: input.status ?? "running",
        created_at: input.created_at ?? timestamp,
        updated_at: input.updated_at ?? timestamp,
    });
    if (!helper)
        throw new Error("helper call input did not include a usable helper id");
    await ensureDir(agentThreadDir(store, runId, ownerThreadId));
    await appendJsonl(agentThreadHelpersPath(store, runId, ownerThreadId), helper);
    await adjustHelperCounts(store, runId, ownerThreadId, { running: 1 });
    return helper;
}
export async function patchHelperCall(store, runId, ownerThreadId, helperId, patch) {
    const owner = safeThreadId(ownerThreadId);
    const current = (await listAgentThreadHelpers(store, runId, owner)).find((helper) => helper.helper_id === helperId);
    if (!current)
        return undefined;
    const status = patch.status ?? current.status;
    const next = normalizeHelperCall(runId, owner, {
        ...current,
        ...withoutUndefined(patch),
        status,
        updated_at: nowIso(),
        completed_at: isTerminalHelperStatus(status) ? (patch.completed_at ?? current.completed_at ?? nowIso()) : patch.completed_at,
    });
    if (!next)
        return undefined;
    await appendJsonl(agentThreadHelpersPath(store, runId, owner), next);
    if (!isTerminalHelperStatus(current.status) && isTerminalHelperStatus(next.status)) {
        await adjustHelperCounts(store, runId, owner, {
            running: -1,
            completed: next.status === "completed" ? 1 : 0,
            failed: next.status === "failed" || next.status === "cancelled" ? 1 : 0,
        });
    }
    return next;
}
export async function patchHelperCallByBackground(store, runId, backgroundId, patch) {
    const helper = await findAgentThreadHelperByBackground(store, runId, backgroundId);
    if (!helper)
        return undefined;
    return patchHelperCall(store, runId, helper.owner_thread_id, helper.helper_id, patch);
}
function normalizeThread(runId, raw) {
    const threadId = safeThreadId(raw?.thread_id ?? raw?.agent_name);
    if (!threadId)
        return undefined;
    const timestamp = raw?.created_at ?? raw?.updated_at ?? nowIso();
    return {
        version: 1,
        thread_id: threadId,
        run_id: runId,
        agent_name: sanitizeText(raw?.agent_name ?? threadId).trim() || threadId,
        role: raw?.role === "lead" ? "lead" : "member",
        role_label: optionalText(raw?.role_label),
        capability_profile: optionalText(raw?.capability_profile),
        parent_thread_id: optionalText(raw?.parent_thread_id),
        opencode_session_id: optionalText(raw?.opencode_session_id),
        background_id: optionalText(raw?.background_id),
        agent_call_id: optionalText(raw?.agent_call_id),
        prompt_ref: optionalText(raw?.prompt_ref),
        tool_manifest_ref: optionalText(raw?.tool_manifest_ref),
        memory_ref: optionalText(raw?.memory_ref),
        status: normalizeStatus(raw?.status),
        allowed_tools: stringList(raw?.allowed_tools),
        denied_tools: stringList(raw?.denied_tools),
        allowed_skills: stringList(raw?.allowed_skills),
        denied_skills: stringList(raw?.denied_skills),
        allowed_mcps: stringList(raw?.allowed_mcps),
        denied_mcps: stringList(raw?.denied_mcps),
        helper_limits: {
            max_depth: nonNegativeInteger(raw?.helper_limits?.max_depth),
            max_parallel_helpers: nonNegativeInteger(raw?.helper_limits?.max_parallel_helpers),
        },
        helper_counts: {
            running: nonNegativeInteger(raw?.helper_counts?.running),
            completed: nonNegativeInteger(raw?.helper_counts?.completed),
            failed: nonNegativeInteger(raw?.helper_counts?.failed),
        },
        read_paths: stringList(raw?.read_paths),
        write_paths: stringList(raw?.write_paths),
        created_at: timestamp,
        updated_at: raw?.updated_at ?? timestamp,
        completed_at: optionalText(raw?.completed_at),
    };
}
function normalizeHelperCall(runId, ownerThreadId, raw) {
    const helperId = optionalText(raw?.helper_id);
    if (!helperId)
        return undefined;
    const timestamp = raw?.created_at ?? raw?.updated_at ?? nowIso();
    return {
        version: 1,
        helper_id: helperId,
        run_id: runId,
        owner_thread_id: safeThreadId(raw?.owner_thread_id ?? ownerThreadId),
        target_agent: optionalText(raw?.target_agent) ?? "helper",
        prompt_ref: optionalText(raw?.prompt_ref) ?? "",
        prompt_summary: optionalText(raw?.prompt_summary) ?? "",
        child_session_id: optionalText(raw?.child_session_id),
        background_id: optionalText(raw?.background_id),
        agent_call_id: optionalText(raw?.agent_call_id),
        status: normalizeHelperStatus(raw?.status),
        allowed_tools: stringList(raw?.allowed_tools),
        denied_tools: stringList(raw?.denied_tools),
        allowed_skills: stringList(raw?.allowed_skills),
        denied_skills: stringList(raw?.denied_skills),
        created_at: timestamp,
        updated_at: raw?.updated_at ?? timestamp,
        completed_at: optionalText(raw?.completed_at),
        result_ref: optionalText(raw?.result_ref),
        error: optionalText(raw?.error),
    };
}
function normalizeStatus(status) {
    return status ?? "created";
}
function normalizeHelperStatus(status) {
    if (status === "queued" || status === "running" || status === "completed" || status === "failed" || status === "cancelled")
        return status;
    return "running";
}
function safeThreadId(value) {
    return sanitizeText(typeof value === "string" ? value : "").trim();
}
function optionalText(value) {
    const text = sanitizeText(typeof value === "string" ? value : "").trim();
    return text || undefined;
}
function stringList(value) {
    if (!Array.isArray(value))
        return [];
    return value.map((item) => optionalText(item)).filter((item) => Boolean(item));
}
function nonNegativeInteger(value) {
    return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
}
function isTerminalStatus(status) {
    return status === "completed" || status === "failed" || status === "cancelled";
}
function isTerminalHelperStatus(status) {
    return status === "completed" || status === "failed" || status === "cancelled";
}
async function adjustHelperCounts(store, runId, threadId, delta) {
    const thread = await getAgentThread(store, runId, threadId);
    if (!thread)
        return;
    await patchAgentThread(store, runId, threadId, {
        helper_counts: {
            running: Math.max(0, thread.helper_counts.running + (delta.running ?? 0)),
            completed: Math.max(0, thread.helper_counts.completed + (delta.completed ?? 0)),
            failed: Math.max(0, thread.helper_counts.failed + (delta.failed ?? 0)),
        },
    });
}
function withoutUndefined(value) {
    return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
}
//# sourceMappingURL=store.js.map