import { appendContextMetric } from "./telemetry.js";
import { getContextBundle, listContextBundles, normalizeContextBundleId, readContextBundleBody } from "./store.js";
import { recordRunBudgetContextInjection } from "../run-budget/index.js";
export async function planContextInjection(store, input) {
    if (!store.config.context_ledger.enabled) {
        return {
            turn_kind: input.turn_kind,
            run_id: input.run_id,
            actor: input.actor,
            target_agent: input.target_agent,
            target_thread_id: input.target_thread_id,
            attachments: [],
            inline_text: boundedInlineText(store, input),
            injected_chars: 0,
            warnings: ["context ledger disabled"],
        };
    }
    const bundles = await resolveBundles(store, input);
    const warnings = [];
    const maxAttachments = store.config.context_policy.max_context_attachments_per_message;
    const selected = bundles.slice(0, maxAttachments);
    if (bundles.length > maxAttachments) {
        warnings.push(`context attachments truncated: ${bundles.length} -> ${maxAttachments}`);
    }
    const attachments = await Promise.all(selected.map((bundle) => attachBundle(store, input.run_id, input.turn_kind, bundle)));
    const inlineText = boundedInlineText(store, input);
    const injectedChars = inlineText.length + attachments.reduce((total, attachment) => total + attachment.char_count, 0);
    const categoryCounts = new Map();
    for (const attachment of attachments) {
        const count = categoryCounts.get(attachment.category) ?? 0;
        categoryCounts.set(attachment.category, count + 1);
    }
    for (const [category, count] of categoryCounts) {
        if (count >= store.config.context_policy.duplicate_bundle_warning_threshold) {
            warnings.push(`many ${category} context attachments in one turn: ${count}`);
        }
    }
    const plan = {
        turn_kind: input.turn_kind,
        run_id: input.run_id,
        actor: input.actor,
        target_agent: input.target_agent,
        target_thread_id: input.target_thread_id,
        attachments,
        inline_text: inlineText,
        injected_chars: injectedChars,
        warnings,
    };
    await appendContextMetric(store, input.run_id, {
        event: "context_planned",
        actor: input.actor,
        turn_kind: input.turn_kind,
        injected_chars: injectedChars,
        summary: `${attachments.length} context attachment(s), ${inlineText.length} inline char(s)`,
        metadata: {
            target_agent: input.target_agent,
            target_thread_id: input.target_thread_id,
            attachment_ids: attachments.map((attachment) => attachment.bundle_id),
            warnings,
        },
    });
    await recordRunBudgetContextInjection(store, input.run_id, {
        injected_chars: injectedChars,
        attachments: attachments.map((attachment) => ({
            bundle_id: attachment.bundle_id,
            category: attachment.category,
            char_count: attachment.char_count,
        })),
    });
    return plan;
}
async function resolveBundles(store, input) {
    if (input.bundle_ids?.length) {
        const bundles = [];
        for (const bundleRef of input.bundle_ids) {
            const bundle = await getContextBundle(store, input.run_id, normalizeContextBundleId(bundleRef));
            if (bundle)
                bundles.push(bundle);
        }
        return bundles;
    }
    const categories = input.include_categories ?? defaultCategoriesForTurn(input.turn_kind);
    const bundles = await listContextBundles(store, input.run_id);
    return bundles.filter((bundle) => categories.includes(bundle.category));
}
function defaultCategoriesForTurn(turnKind) {
    switch (turnKind) {
        case "member_launch":
            return ["stable", "warm", "heavy"];
        case "message_delivery":
            return ["stable", "warm", "heavy", "memory"];
        case "root_chat":
            return ["warm", "heavy", "memory"];
        case "session_compacting":
            return ["stable", "memory", "heavy"];
        case "lab_meeting_resume":
            return ["warm", "hot", "heavy", "memory"];
    }
}
async function attachBundle(store, runId, turnKind, bundle) {
    const renderMode = renderModeForBundle(store, turnKind, bundle);
    const inlineText = renderMode === "inline"
        ? truncateInline(await readContextBundleBody(store, runId, bundle), store.config.context_ledger.max_inline_chars)
        : undefined;
    const charCount = renderMode === "ref"
        ? bundle.body_ref.length
        : inlineText?.length ?? Math.min(bundle.digest.length, store.config.context_ledger.max_digest_chars);
    return {
        bundle_id: bundle.bundle_id,
        category: bundle.category,
        scope: bundle.scope,
        title: bundle.title,
        render_mode: renderMode,
        digest: bundle.digest,
        inline_text: inlineText,
        body_ref: bundle.body_ref,
        body_sha256: bundle.body_sha256,
        byte_length: bundle.byte_length,
        char_count: charCount,
        reason: reasonForBundle(turnKind, bundle.category, renderMode),
    };
}
function truncateInline(value, maxChars) {
    if (value.length <= maxChars)
        return value;
    return `${value.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
}
function renderModeForBundle(store, turnKind, bundle) {
    if (turnKind === "member_launch") {
        if (bundle.category === "stable") {
            return store.config.context_policy.stable_contract_inline_on_launch ? "inline" : "digest";
        }
        if (bundle.category === "hot")
            return "inline";
        return "ref";
    }
    if (turnKind === "message_delivery") {
        if (bundle.category === "stable") {
            return store.config.context_policy.stable_contract_digest_after_launch ? "digest" : "ref";
        }
        if (bundle.category === "hot")
            return "inline";
        if (bundle.category === "memory")
            return "digest";
        return "ref";
    }
    if (turnKind === "session_compacting") {
        if (bundle.category === "memory")
            return "inline";
        if (bundle.category === "stable")
            return "digest";
        if (bundle.category === "warm" || bundle.category === "hot")
            return "digest";
        return "ref";
    }
    if (turnKind === "lab_meeting_resume") {
        if (bundle.category === "hot")
            return "inline";
        if (bundle.category === "warm" || bundle.category === "memory")
            return "digest";
        return "ref";
    }
    if (turnKind === "root_chat") {
        if (bundle.category === "hot")
            return "inline";
        if (bundle.category === "warm" || bundle.category === "memory" || bundle.category === "stable")
            return "digest";
        return "ref";
    }
    if (bundle.category === "hot")
        return "inline";
    if (bundle.category === "warm" || bundle.category === "memory")
        return store.config.context_policy.warm_context_default;
    return "ref";
}
function boundedInlineText(store, input) {
    const pieces = [input.hot_text, input.feedback_text].filter((piece) => Boolean(piece?.trim()));
    const text = pieces.join("\n\n").trim();
    const limit = Math.min(input.max_inline_chars ?? store.config.context_policy.hot_context_max_chars, store.config.context_policy.hot_context_max_chars);
    if (text.length <= limit)
        return text;
    return `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}…`;
}
function reasonForBundle(turnKind, category, renderMode) {
    if (renderMode === "ref")
        return `${category} context is durable and available by reference for ${turnKind}.`;
    if (renderMode === "digest")
        return `${category} context is summarized to keep the ${turnKind} prompt stable.`;
    return `${category} context is small or launch-critical enough to inline for ${turnKind}.`;
}
//# sourceMappingURL=planner.js.map