/**
 * Quick Capture API
 *
 * POST /api/capture — one-shot JSON response. Used by the file-import
 *   pipeline (which processes files in parallel and only needs the final
 *   mutations list per file).
 *
 * POST /api/capture/stream — Server-Sent Events. Emits progress events
 *   (stage labels, per-mutation deltas) as the agent works, plus a final
 *   `done` event with the full mutations list and agent message.
 *
 * Both routes share the same prompt-building + agent invocation logic.
 * Each capture runs under a unique `capture:${uuid}` agent session so
 * conversation history from one capture doesn't bleed into the next.
 */
import { randomUUID } from 'crypto';
import { parseFollowups } from '../../agent/followups.js';
import { addInferenceUsage, createCloudInferenceBillingClient, createInferenceUsageSummary, isStudiographCreditsMode, } from '../../services/cloud-inference-billing.js';
import { reserveCloudInference, settleCloudInference, releaseCloudInference, } from '../cloud-billing-flow.js';
function serializeMutation(m) {
    return {
        action: m.action,
        repo: m.repo,
        entityType: m.entityType,
        entityId: m.entityId,
    };
}
function buildCapturePrompt(text, repo) {
    const repoHint = repo ? `\nTarget folder: "${repo}".` : '';
    return `QUICK CAPTURE — Parse the text and create the right entities. Follow these rules precisely.

FIELD RULES (every entity type)
- Fill every field the text supports. Don't leave extracted data on the table.
- Never emit empty strings ('') for optional fields — omit the field entirely.
- Body content: write a real summary in prose/bullets. Never write a placeholder like "-" or leave it blank when the source has substance.
- Dates: always use ISO "YYYY-MM-DD". Resolve relative dates ("Monday", "next week") against today (${new Date().toISOString().slice(0, 10)}).
- Wikilinks: use [[entity-id]] in structured fields (never [[id|Alias]] there). Use [[id|Display]] only inside prose body text.
- Tags are for themes/topics (e.g. "business-development", "ai"). Do NOT tag with an organization or person slug — use the relational field (organization, attendees, related_organizations, assignee) instead.

PER-TYPE RULES
- person: "name" must be the full human name as it appears in the source. If only a first name is given and there's no surname, use "First (Organization)" (e.g., "Andrew (Simpson Thacher)") — never truncate to just "Andrew". Extract role/email/location whenever the source mentions them.
- event: "date" = when the gathering occurred (from the source), NOT today's capture date. For single-day events leave "start_date" off; for multi-day events set "start_date" (the first day) and "date" (the last day). Only fall back to today if the source truly has no date signal. Put attendees in "attendees" as wikilinks. "type" defaults to "meeting" — set it to call/workshop/review/presentation/conference/other if the source clearly indicates a different kind of gathering.
- task: use "assignee" (the schema field). Do NOT invent "assigned_to". Set "date" (the due date) whenever the text mentions one (e.g. "by Friday", "May 4-5" → use the earliest actionable date).

PROCESS
1. Identify every entity worth creating (note, event, task, document, deck, person, organization, project, reference). If ambiguous, default to "note".
2. Call create_entity for each. Reference existing entities via wikilinks.
3. If — and only if — there is genuinely nothing meaningful to capture (error pages, redirect-only fetches, empty pages, pure noise), call the nothing_to_capture tool with a brief one-line reason and stop. Do NOT use this tool when the input is a normal note, article, or message you should save.
4. After tool calls, output an empty assistant turn — no confirmation sentence, no summary, no followups marker. The UI surfaces results from the tool calls themselves.
${repoHint}

User's input:
${text.trim()}`;
}
/**
 * Map an agent tool name to a user-facing stage label.
 * Only tools that matter for the "what's the agent doing right now" surface;
 * unrecognised tools fall back to a generic working label.
 */
function stageForTool(toolName) {
    switch (toolName) {
        case 'create_entity': return 'Writing entry…';
        case 'update_entity': return 'Amending entry…';
        case 'delete_entity': return 'Removing entry…';
        case 'rename_entity': return 'Renaming entry…';
        case 'move_entity': return 'Moving entry…';
        case 'transfer_entity': return 'Transferring entry…';
        case 'create_folder': return 'Creating folder…';
        case 'create_subfolder': return 'Creating subfolder…';
        case 'restore_file': return 'Restoring entry…';
        case 'list_file_history':
        case 'list_folder_history':
        case 'list_recent_deletions':
        case 'read_file_at_commit':
        case 'get_entity_diff': return 'Reading history…';
        case 'search_entities':
        case 'list_entities':
        case 'get_entity':
        case 'find_related': return 'Searching workspace…';
        case 'validate_wikilinks': return 'Checking wikilinks…';
        default: return 'Working…';
    }
}
export async function registerCaptureRoutes(fastify, agent) {
    // In Studiograph Credits gateway mode the orchestrator routes inference
    // through the cloud stream fn, which meters server-side — so the legacy
    // reserve/settle path is skipped here exactly as it is for chat.
    const creditsGatewayMode = isStudiographCreditsMode();
    // POST /api/capture — single-shot JSON response (used by import pipeline)
    fastify.post('/api/capture', async (req, reply) => {
        const { text, repo } = req.body;
        if (!text || typeof text !== 'string' || !text.trim()) {
            return reply.status(400).send({ error: 'text is required' });
        }
        const user = req.user ?? null;
        const mutations = [];
        let nothingToCapture = null;
        const sessionId = `capture:${randomUUID()}`;
        const capturePrompt = buildCapturePrompt(text, repo);
        // Capture is hosted inference too — meter it through the same Cloud
        // billing gate as chat. On managed workspaces with required billing this
        // blocks capture (and the import pipeline, which calls this route) when
        // credits aren't ready, instead of running the model unmetered.
        const cloudBilling = createCloudInferenceBillingClient();
        let reservation = null;
        try {
            reservation = creditsGatewayMode
                ? null
                : await reserveCloudInference(cloudBilling, agent, 'Hosted Studiograph capture inference.');
            const response = await agent.getPool().withAgent(sessionId, {
                user,
                onMutation: (m) => { mutations.push(m); },
                onNothingToCapture: (reason) => { nothingToCapture = reason; },
            }, async (rawAgent) => {
                return new Promise((resolve, reject) => {
                    let content = '';
                    const usage = createInferenceUsageSummary();
                    const unsub = rawAgent.subscribe((event) => {
                        if (event.type === 'message_end' && event.message?.role === 'assistant') {
                            addInferenceUsage(usage, event.message.usage);
                            content = event.message.content
                                .filter((b) => b.type === 'text')
                                .map((b) => b.text)
                                .join('');
                        }
                        else if (event.type === 'agent_end') {
                            unsub();
                            resolve({ content, usage });
                        }
                    });
                    rawAgent.prompt(capturePrompt).catch((err) => { unsub(); reject(err); });
                });
            }, { userId: user?.id });
            await settleCloudInference(cloudBilling, reservation, response.usage);
            reservation = null;
            return reply.send({
                success: true,
                message: parseFollowups(response.content).content,
                entities: mutations.filter(m => m.action === 'created').map(serializeMutation),
                mutations: mutations.map(serializeMutation),
                nothingToCapture,
            });
        }
        catch (err) {
            await releaseCloudInference(cloudBilling, reservation, 'Capture failed before billing settlement.');
            fastify.log.error(err, 'Capture error');
            return reply.status(500).send({ error: err.message || 'Capture failed' });
        }
    });
    // POST /api/capture/stream — SSE progress stream
    fastify.post('/api/capture/stream', async (req, reply) => {
        const { text, repo } = req.body;
        if (!text || typeof text !== 'string' || !text.trim()) {
            return reply.status(400).send({ error: 'text is required' });
        }
        const user = req.user ?? null;
        const mutations = [];
        let nothingToCapture = null;
        const sessionId = `capture:${randomUUID()}`;
        const capturePrompt = buildCapturePrompt(text, repo);
        reply.raw.setHeader('Content-Type', 'text/event-stream');
        reply.raw.setHeader('Cache-Control', 'no-cache');
        reply.raw.setHeader('Connection', 'keep-alive');
        reply.raw.flushHeaders();
        let streamDead = false;
        const send = (data) => {
            if (streamDead || !reply.raw.writable) {
                streamDead = true;
                return;
            }
            try {
                reply.raw.write(`data: ${JSON.stringify(data)}\n\n`);
            }
            catch {
                streamDead = true;
            }
        };
        reply.raw.on('close', () => { streamDead = true; });
        reply.raw.on('error', () => { streamDead = true; });
        send({ type: 'stage', label: 'Reading your input…' });
        // Meter capture inference through the same Cloud billing gate as chat
        // (see the one-shot route above for rationale).
        const cloudBilling = createCloudInferenceBillingClient();
        let reservation = null;
        try {
            reservation = creditsGatewayMode
                ? null
                : await reserveCloudInference(cloudBilling, agent, 'Hosted Studiograph capture inference.');
            const response = await agent.getPool().withAgent(sessionId, {
                user,
                onMutation: (m) => {
                    mutations.push(m);
                    const serialized = serializeMutation(m);
                    send({ type: 'mutation', ...serialized });
                    // After the first mutation, surface a running count so the
                    // progress surface reads "Writing entry 2…" instead of staying
                    // on the generic working label.
                    const created = mutations.filter(x => x.action === 'created').length;
                    const updated = mutations.filter(x => x.action === 'updated').length;
                    const parts = [];
                    if (created)
                        parts.push(`${created} created`);
                    if (updated)
                        parts.push(`${updated} amended`);
                    if (parts.length)
                        send({ type: 'stage', label: parts.join(', ') });
                },
                onNothingToCapture: (reason) => { nothingToCapture = reason; },
            }, async (rawAgent) => {
                return new Promise((resolve, reject) => {
                    let content = '';
                    let messageStarted = false;
                    const usage = createInferenceUsageSummary();
                    const unsub = rawAgent.subscribe((event) => {
                        if (event.type === 'message_start' && !messageStarted) {
                            messageStarted = true;
                            send({ type: 'stage', label: 'Thinking…' });
                        }
                        else if (event.type === 'tool_execution_start' && event.toolName) {
                            send({ type: 'stage', label: stageForTool(event.toolName) });
                        }
                        else if (event.type === 'message_end' && event.message?.role === 'assistant') {
                            addInferenceUsage(usage, event.message.usage);
                            content = event.message.content
                                .filter((b) => b.type === 'text')
                                .map((b) => b.text)
                                .join('');
                        }
                        else if (event.type === 'agent_end') {
                            unsub();
                            resolve({ content, usage });
                        }
                    });
                    rawAgent.prompt(capturePrompt).catch((err) => { unsub(); reject(err); });
                });
            }, { userId: user?.id });
            await settleCloudInference(cloudBilling, reservation, response.usage);
            reservation = null;
            send({
                type: 'done',
                message: parseFollowups(response.content).content,
                mutations: mutations.map(serializeMutation),
                nothingToCapture,
            });
            reply.raw.end();
        }
        catch (err) {
            await releaseCloudInference(cloudBilling, reservation, 'Capture failed before billing settlement.');
            fastify.log.error(err, 'Capture stream error');
            send({ type: 'error', error: err.message || 'Capture failed' });
            reply.raw.end();
        }
        // Prevent Fastify from trying to send its own response
        return reply;
    });
}
//# sourceMappingURL=capture-api.js.map