import { randomUUID } from "node:crypto";
import { getSession } from "../lib/neo4j.js";
import { computeEmbedding } from "../lib/embeddings.js";
import { writeNodeWithEdges, } from "../../../../../lib/graph-write/dist/index.js";
import { redactSecrets, } from "../../../../../lib/task-secrets/dist/index.js";
export async function workCreate(params) {
    const { accountId, name, description, status = "pending", priority = "normal", dueDate, sessionKey, raisedBy, affects, kind, inputsProvided, inputs, inputSchema, raisedDuringConversationKey, createdBy, } = params;
    const taskId = randomUUID();
    const now = new Date().toISOString();
    const props = {
        taskId,
        accountId,
        name,
        description,
        status,
        priority,
        createdAt: now,
        updatedAt: now,
    };
    if (dueDate)
        props.dueDate = dueDate;
    if (kind)
        props.kind = kind;
    if (inputsProvided && inputsProvided.length > 0)
        props.inputsProvided = inputsProvided;
    if (status === "running")
        props.startedAt = now;
    // outcome contract — persist the form payload as `inputs.<field>`
    // props after stripping any secret-tagged keys via redactSecrets. Schema
    // is the form definition's authority; writers never re-classify.
    if (inputs) {
        if (!inputSchema) {
            process.stderr.write(`[task] redact-no-schema kind=${kind ?? "unknown"} taskId=${taskId} fieldsCount=${Object.keys(inputs).length}\n`);
        }
        const { redacted, droppedFields, droppedFieldNames } = redactSecrets(inputs, inputSchema);
        for (const [key, value] of Object.entries(redacted)) {
            props[`inputs.${key}`] = value;
        }
        if (droppedFields > 0) {
            process.stderr.write(`[task] redacted kind=${kind ?? "unknown"} taskId=${taskId} droppedFields=${droppedFields} fields=${droppedFieldNames.join(",")}\n`);
        }
    }
    const embeddingText = `Task: ${name} — ${description} status: ${status} priority: ${priority}`;
    const embedding = await computeEmbedding(embeddingText);
    if (embedding.length > 0)
        props.embedding = embedding;
    // Build the relationships list that satisfies the write doctrine. The
    // task's linking sources are, in priority order: the current
    // Conversation (via sessionKey), the raising Person, and any entities
    // the task will affect. At least one must resolve — a Task that affects
    // nothing and was raised by no one and belongs to no session is noise,
    // not knowledge (see .docs/neo4j.md Write doctrine).
    const relationships = [];
    const neo4jSession = getSession();
    try {
        let conversationElementId = null;
        if (sessionKey) {
            // Do NOT MERGE (c:Conversation {sessionKey}) — the original
            // implementation created a Conversation with no edges when the
            // session didn't already exist, spawning an orphan to avoid
            // spawning an orphan. Pre-resolve via MATCH; skip the PART_OF
            // edge if the Conversation is missing (caller may still have
            // raisedBy/affects).
            const r = await neo4jSession.run(`MATCH (c:Conversation {sessionKey: $sessionKey}) RETURN elementId(c) AS id LIMIT 1`, { sessionKey });
            if (r.records.length > 0) {
                conversationElementId = r.records[0].get("id");
                relationships.push({
                    type: "PART_OF",
                    direction: "outgoing",
                    targetNodeId: conversationElementId,
                });
            }
        }
        // process-provenance edge — written ALONGSIDE PART_OF, not in
        // place of it. PART_OF carries commitment-tracking semantics for the
        // existing :Task callers (workflow runner, agent commitment); the new
        // RAISED_DURING edge carries action-provenance semantics (what
        // conversation initiated this action). Resolves the same Conversation
        // when raisedDuringConversationKey === sessionKey, or a different one
        // when the Task was raised in a different surface (e.g. cloudflare
        // setup endpoint) but still belongs to a separate session.
        const raisedDuringKey = raisedDuringConversationKey ?? sessionKey;
        if (raisedDuringKey) {
            const targetId = raisedDuringKey === sessionKey && conversationElementId
                ? conversationElementId
                : await (async () => {
                    const r = await neo4jSession.run(`MATCH (c:Conversation {sessionKey: $sessionKey}) RETURN elementId(c) AS id LIMIT 1`, { sessionKey: raisedDuringKey });
                    return r.records.length > 0 ? r.records[0].get("id") : null;
                })();
            if (targetId) {
                relationships.push({
                    type: "RAISED_DURING",
                    direction: "outgoing",
                    targetNodeId: targetId,
                });
            }
        }
        if (raisedBy) {
            relationships.push({
                type: "RAISED_BY",
                direction: "outgoing",
                targetNodeId: raisedBy,
            });
        }
        if (affects) {
            for (const entityId of affects) {
                relationships.push({
                    type: "AFFECTS",
                    direction: "outgoing",
                    targetNodeId: entityId,
                });
            }
        }
        if (relationships.length === 0) {
            throw new Error("Write doctrine violated: task-create requires at least one link — a valid session with an existing Conversation, a raisedBy Person, or at least one affects entity. Pass raisedBy or affects, or call within an admin session.");
        }
        await writeNodeWithEdges({
            session: neo4jSession,
            labels: ["Task"],
            props,
            relationships,
            createdBy,
        });
        // Process-provenance lifecycle signal. Only emitted when the
        // caller declared a `kind` — commitment-tracking callers stay silent on
        // this channel. `raisedDuring` reports whether the RAISED_DURING edge
        // resolved so operators can grep "kind=X raisedDuring=none" to find
        // actions that fired outside any conversation context.
        if (kind) {
            const raisedDuringNote = relationships.some((r) => r.type === "RAISED_DURING")
                ? (raisedDuringConversationKey ?? sessionKey ?? "unknown")
                : "none";
            process.stderr.write(`[task] action-start kind=${kind} taskId=${taskId} raisedDuring=${raisedDuringNote}\n`);
        }
        return { taskId };
    }
    finally {
        await neo4jSession.close();
    }
}
//# sourceMappingURL=work-create.js.map