import { z } from 'zod';
/** Default serialized-byte budget. Over this, a document overflows to an outline. */
export const DEFAULT_OUTLINE_BUDGET_BYTES = 24_000;
/**
 * Reusable outline arm for a tool's discriminated-union `output`. Pair it with
 * the tool's full-payload schema so the parity linter validates each branch:
 *
 * ```ts
 * output: z.discriminatedUnion('kind', [
 *   FullLabel.extend({ kind: z.literal('full') }),
 *   OUTLINE_VARIANT,
 * ]),
 * ```
 */
export const OUTLINE_VARIANT = z.object({
    kind: z.literal('outline'),
    sections: z
        .array(z.object({
        name: z.string().describe('Section identifier — pass in `sections` to retrieve it'),
        bytes: z.number().int().nonnegative().describe('Serialized byte size of the section'),
    }))
        .describe('Available sections, largest first'),
    notice: z.string().describe('How to re-call the tool for specific sections'),
});
/** Default extractor: one section per top-level key, sized by serialized length. */
function defaultExtract(doc) {
    return Object.entries(doc).map(([name, value]) => ({
        name,
        bytes: JSON.stringify(value)?.length ?? 0,
    }));
}
/** Default notice: re-call instruction naming the three largest sections. */
function defaultNotice(sections) {
    const examples = sections
        .slice(0, 3)
        .map((s) => s.name)
        .join(', ');
    return `Record too large to inline. Re-call this tool with sections:[...] to retrieve specific sections — e.g. ${examples}.`;
}
/**
 * Returns the document whole when it fits the budget, or a section outline when
 * it overflows. The caller spreads the result into a discriminated-union `output`
 * keyed on `kind` ({@link OUTLINE_VARIANT} supplies the outline arm).
 *
 * Single-entry short-circuit: a document with fewer than two sections is returned
 * whole even when over budget — an outline of one section would cost a round-trip
 * whose only possible `sections` arg returns the same bytes. (A lone section that
 * itself exceeds the budget is a known limitation; sub-section outlining is out
 * of scope.)
 *
 * @example
 * ```ts
 * async handler(input) {
 *   const doc = await fetchLabel(input.query);
 *   if (input.sections?.length) {
 *     return { ...selectSections(doc, input.sections), kind: 'full' as const };
 *   }
 *   return outlineOnOverflow(doc, { budget: 24_000 });
 * }
 * ```
 */
export function outlineOnOverflow(doc, options) {
    const budget = options?.budget ?? DEFAULT_OUTLINE_BUDGET_BYTES;
    if (JSON.stringify(doc).length <= budget) {
        return { ...doc, kind: 'full' };
    }
    const sections = (options?.extract ?? defaultExtract)(doc).sort((a, b) => b.bytes - a.bytes);
    // Single-entry short-circuit — nothing to choose between.
    if (sections.length < 2) {
        return { ...doc, kind: 'full' };
    }
    const notice = (options?.notice ?? defaultNotice)(sections);
    return { kind: 'outline', sections, notice };
}
/**
 * Projects a document down to the requested sections plus any always-kept
 * metadata keys — the selection-path counterpart to {@link outlineOnOverflow}.
 * The handler re-fetches the document, then slices it to what the agent asked
 * for. Pure top-level key projection; supply a custom selector when "section"
 * means something other than a top-level key.
 *
 * @param doc - The full document.
 * @param want - Section identifiers the agent requested (from the outline).
 * @param options.alwaysKeep - Metadata keys to retain regardless (ids, timestamps).
 */
export function selectSections(doc, want, options) {
    const keep = new Set([...want, ...(options?.alwaysKeep ?? [])]);
    return Object.fromEntries(Object.entries(doc).filter(([key]) => keep.has(key)));
}
/**
 * Renders an outline payload to MCP `content[]` — the markdown twin of the
 * outline's `structuredContent`, so `format()`-parity holds. Drop into a tool's
 * `format`:
 *
 * ```ts
 * format: (r) => (r.kind === 'outline' ? formatOutline(r) : renderFull(r)),
 * ```
 */
export function formatOutline(outline) {
    const lines = [
        `**${outline.sections.length} sections available** (record too large to inline)`,
        '',
        ...outline.sections.map((s) => `- \`${s.name}\` — ${s.bytes} bytes`),
        '',
        outline.notice,
    ];
    return [{ type: 'text', text: lines.join('\n') }];
}
//# sourceMappingURL=outlineOnOverflow.js.map