import { channelDisplayName, formatMessageHeading } from "./message-format.js";
import { downloadAndResizeForVision, resizeBase64ForVision } from "../tools/screenshot.js";
import { logger } from "../logger.js";
/**
 * Format the current wall-clock time with full date, long time, and timezone.
 * Used at the top of each wake/interrupt/post-compaction/calendar/auto-
 * screenshot context block so the agent always has an authoritative "now"
 * even though the system prompt is time-agnostic (to allow aggressive
 * subprocess pre-warming).
 *
 * Exported so every user-visible context message uses the same format —
 * this matters because the LLM picks up "now" from the most recent
 * timestamp it sees, and inconsistent formats reduce cache hits.
 */
export function formatNowWithTz() {
    const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
    const pretty = new Date().toLocaleString("en-US", {
        timeZone: tz,
        dateStyle: "full",
        timeStyle: "long",
    });
    return `${pretty} (${tz})`;
}
/**
 * Resolve an image attachment to a ContentBlock.
 * Handles both base64 and URL sources, resizing to fit Claude's vision constraints.
 */
async function resolveImageAttachment(att) {
    if (att.base64) {
        const resized = await resizeBase64ForVision(att.base64);
        return {
            type: "image",
            source: {
                type: "base64",
                media_type: (att.mimeType ?? "image/png"),
                data: resized,
            },
        };
    }
    if (att.url) {
        const resized = await downloadAndResizeForVision(att.url);
        if (resized) {
            return {
                type: "image",
                source: {
                    type: "base64",
                    media_type: resized.mimeType,
                    data: resized.base64,
                },
            };
        }
        logger.warn(`Could not download/resize attachment image after retries, using placeholder: ${att.url}`);
        return {
            type: "text",
            text: "[Image attachment: download failed after retries, image unavailable. Ask the user to resend if needed.]",
        };
    }
    return null;
}
/**
 * Emit content blocks for a single message: a text block describing the message
 * followed by image blocks for any image attachments. This keeps each message's
 * text and visuals adjacent so the model can associate them.
 */
async function emitMessageBlocks(msg, blocks) {
    const lines = [];
    lines.push(formatMessageHeading(msg));
    if (msg.text)
        lines.push(msg.text);
    if (msg.attachments.length > 0) {
        for (const att of msg.attachments) {
            const location = att.url ?? att.localPath ?? "(embedded)";
            lines.push(`Attachment [${att.type}]: ${location}${att.filename ? ` (${att.filename})` : ""}`);
        }
    }
    blocks.push({ type: "text", text: lines.join("\n") });
    for (const att of msg.attachments) {
        if (att.type !== "image")
            continue;
        const block = await resolveImageAttachment(att);
        if (block)
            blocks.push(block);
    }
}
/**
 * Build the context message injected on each agent wake.
 * Returns a multimodal content block array. Each message's text and image
 * attachments are kept adjacent so the model can associate them correctly.
 * All images are resized to fit Claude's vision constraints.
 */
export async function buildWakeContext(options) {
    const { trigger, messages, calendarSummary, screenshot, versionUpdate, activeSession, sessionMemos, codingSessionStatus, justOnboarded, justUpgraded, justRestarted, voiceFollowups, realtimeActiveSession, } = options;
    const now = formatNowWithTz();
    const blocks = [];
    // --- Header: wake event + session info + calendar ---
    const header = [];
    header.push(`[Wake Event] trigger=${trigger} time=${now}`);
    if (activeSession) {
        header.push(`[Active session: ${activeSession}]`);
    }
    header.push("");
    if (sessionMemos) {
        header.push(sessionMemos);
        header.push("");
    }
    if (codingSessionStatus && activeSession === "general") {
        header.push(`## Active Coding Task`);
        header.push(codingSessionStatus);
        header.push("");
    }
    if (calendarSummary) {
        header.push("## Upcoming Calendar Events");
        header.push(calendarSummary);
    }
    else {
        header.push("## Calendar");
        header.push("No upcoming events.");
    }
    header.push("");
    if (realtimeActiveSession) {
        header.push("## Realtime Voice Session Active");
        header.push(realtimeActiveSession);
        header.push("");
    }
    if (voiceFollowups) {
        header.push("## Recent Voice Sessions");
        header.push(voiceFollowups);
        header.push("");
    }
    if (justOnboarded) {
        header.push("");
        header.push("## Onboarding Just Completed");
        header.push("Owner onboarding was just completed. This is your first wake cycle as a fully operational agent. " +
            "Send a brief message to the owner through their messaging channel to let them know you are now online and ready to help. " +
            "Keep it warm and concise.");
    }
    if (justUpgraded) {
        header.push("");
        header.push("## Just Upgraded");
        header.push(`You were just upgraded from v${justUpgraded.fromVersion} to v${justUpgraded.toVersion}. ` +
            "Send a brief message to the owner through their messaging channel to let them know the upgrade completed successfully and you are back online.");
    }
    if (justRestarted) {
        header.push("");
        header.push("## Just Restarted");
        header.push("The owner manually triggered a restart and the agent has just come back online. " +
            "Send a brief message to the owner through their messaging channel to let them know you are back online and ready.");
    }
    if (versionUpdate?.hasUpdate) {
        header.push("");
        header.push("## Update Available");
        header.push(`A new version of VisionClaw is available: ${versionUpdate.currentVersion} → ${versionUpdate.latestVersion}`);
        if (versionUpdate.changelog) {
            header.push("");
            header.push(versionUpdate.changelog);
        }
        header.push("");
        header.push("Notify the user about this update through their messaging channel. " +
            "Present the changes in a friendly, non-technical way — focus on what's improved or fixed from the user's perspective, " +
            "not internal implementation details (e.g., say \"better memory\" instead of \"added hybrid search fallback\"). " +
            "Keep it brief and conversational. Ask if they would like to upgrade. " +
            "You can use the `upgrade` tool to perform the upgrade.");
    }
    blocks.push({ type: "text", text: header.join("\n") });
    // --- Messages: each message's text + its attachments together ---
    if (messages.length > 0) {
        for (const msg of messages) {
            await emitMessageBlocks(msg, blocks);
        }
    }
    else if (trigger === "heartbeat") {
        blocks.push({ type: "text", text: "No new messages." });
    }
    // --- Desktop screenshot ---
    if (screenshot?.base64) {
        blocks.push({ type: "text", text: "## Desktop Screenshot" });
        blocks.push({
            type: "image",
            source: {
                type: "base64",
                media_type: "image/png",
                data: screenshot.base64,
            },
        });
    }
    blocks.push({
        type: "text",
        text: "Review the above context and decide what to do. Remember to check any unread message in chat apps. If there is nothing to do, call the `finish` tool.",
    });
    return blocks;
}
/**
 * Build context for an interrupt message — a new user message that arrives
 * while the agent is already processing. Lighter than a full wake context
 * (no screenshot or calendar). Each message's text and attachments are kept
 * adjacent.
 */
export async function buildInterruptContext(options) {
    const { messages } = options;
    const now = formatNowWithTz();
    const blocks = [];
    blocks.push({
        type: "text",
        text: `[Interrupt] New message(s) received at ${now} while you are working.`,
    });
    for (const msg of messages) {
        await emitMessageBlocks(msg, blocks);
    }
    blocks.push({
        type: "text",
        text: "Decide whether to acknowledge, switch tasks, or continue your current work.",
    });
    return blocks;
}
/**
 * Build an interrupt context that includes the fast responder's briefing.
 * The primary agent sees what the user said AND what was already replied,
 * so it can decide whether to follow up or skip.
 */
export function buildInterruptWithBriefing(options) {
    const { messages, fastResponse } = options;
    const now = formatNowWithTz();
    const parts = [];
    parts.push(`[Interrupt — auto-acknowledged] at ${now}`);
    parts.push("");
    for (const msg of messages) {
        parts.push(`- [${channelDisplayName(msg.channel)}] from ${msg.sender} at ${msg.timestamp}: ${msg.text}`);
        if (msg.attachments.length > 0) {
            for (const att of msg.attachments) {
                const location = att.url ?? att.localPath ?? "(embedded)";
                parts.push(`  Attachment [${att.type}]: ${location}${att.filename ? ` (${att.filename})` : ""}`);
            }
        }
    }
    parts.push("");
    parts.push("Your quick-response handler already replied:");
    for (const r of fastResponse.replies) {
        parts.push(`  → [${r.channel}] ${r.sender}: "${r.reply}"`);
    }
    parts.push(`Handler assessment: ${fastResponse.briefForPrimary}`);
    parts.push("");
    if (fastResponse.needsPrimaryAction) {
        parts.push("DO NOT duplicate the replies. Only follow up on this when you finish your current task or you have NEW information.");
    }
    else {
        parts.push("No further action needed — the quick-response handler fully handled this.");
    }
    return [{ type: "text", text: parts.join("\n") }];
}
/**
 * Build a fresh context after compaction. Returns multimodal blocks.
 */
export function buildPostCompactionContext(options) {
    const { config, calendarSummary, pendingMessageCount } = options;
    const now = formatNowWithTz();
    return [
        {
            type: "text",
            text: `[Post-Compaction State Refresh] time=${now}

Your identity: ${config.agentName} <${config.gmail}>
Platform: ${process.platform === "darwin" ? "macOS" : process.platform === "win32" ? "Windows" : process.platform}

## Calendar
${calendarSummary || "No upcoming events."}

## Message Queue
${pendingMessageCount > 0 ? `${pendingMessageCount} message(s) pending in queue.` : "No pending messages."}

Continue from where you left off based on the compaction summary above.`,
        },
    ];
}
//# sourceMappingURL=context.js.map