import type {
  AgentAttachmentResource,
  AgentAttachmentResourceSummary,
  AgentRuntimeRequest,
} from "../types";
import { BALANCED_EVIDENCE_GUIDANCE } from "../../shared/quoteGuidance";
import type { PaperContextRef, TagContextRef } from "../../shared/types";
import {
  buildPaperQuoteCitationGuidance,
  formatPaperCitationLabel,
  formatPaperSourceLabel,
} from "../../modules/contextPanel/paperAttribution";
import type { ContextCachePlan } from "../../contextCache/manager";
import {
  buildAgentEvidenceContextBlock,
  clearAgentEvidenceCache,
  commitAgentCacheEvidenceActivities,
  hydrateAgentEvidenceCache,
  planAgentContextCache,
  type AgentCacheEvidenceActivity,
} from "./cacheManagement";

export { hydrateAgentEvidenceCache };

export type AgentResourceGroup =
  | "selectedPapers"
  | "fullTextPapers"
  | "collections"
  | "tags"
  | "selectedTexts"
  | "screenshots"
  | "attachments"
  | "attachmentPool";

export type AgentResourceRecord = {
  group: AgentResourceGroup;
  key: string;
  signature: string;
  line: string;
};

export type AgentResourceSnapshot = {
  baseScope: Record<string, unknown>;
  resources: Record<AgentResourceGroup, AgentResourceRecord[]>;
};

export type AgentResourceDelta = {
  added: AgentResourceRecord[];
  removed: AgentResourceRecord[];
  changed: AgentResourceRecord[];
  unchanged: number;
};

export type AgentResourceContextPlan = {
  conversationKey: number;
  contextCache?: ContextCachePlan;
  resourceSignature: string;
  stableContextBlock: string;
  resourceSnapshot: AgentResourceSnapshot;
  priorReadBlock?: string;
};

export type AgentPendingReadActivity = AgentCacheEvidenceActivity;

function normalizeText(value: unknown, maxLength = 160): string {
  if (typeof value !== "string") return "";
  return value
    .replace(/[\u0000-\u001F\u007F]/g, " ")
    .replace(/\s+/g, " ")
    .trim()
    .slice(0, maxLength);
}

function normalizePositiveInt(value: unknown): number | undefined {
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
  return Math.floor(parsed);
}

function normalizeRecord(value: unknown): Record<string, unknown> {
  return value && typeof value === "object" && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : {};
}

function stableJson(value: unknown): string {
  return JSON.stringify(stabilizeForJson(value));
}

function stabilizeForJson(value: unknown): unknown {
  if (Array.isArray(value)) return value.map(stabilizeForJson);
  if (!value || typeof value !== "object") return value;
  const record = value as Record<string, unknown>;
  const out: Record<string, unknown> = {};
  for (const key of Object.keys(record).sort()) {
    const child = record[key];
    if (child === undefined) continue;
    out[key] = stabilizeForJson(child);
  }
  return out;
}

function hashText(value: string): string {
  let hash = 5381;
  for (let index = 0; index < value.length; index += 1) {
    hash = ((hash << 5) + hash + value.charCodeAt(index)) | 0;
  }
  return (hash >>> 0).toString(36);
}

function sortResourceRecords<T extends AgentResourceRecord>(records: T[]): T[] {
  return [...records].sort((a, b) => {
    const groupCompare = a.group.localeCompare(b.group);
    if (groupCompare) return groupCompare;
    const keyCompare = a.key.localeCompare(b.key);
    if (keyCompare) return keyCompare;
    return a.signature.localeCompare(b.signature);
  });
}

function paperKey(entry: PaperContextRef): string {
  return `${normalizePositiveInt(entry.itemId) || 0}:${
    normalizePositiveInt(entry.contextItemId) || 0
  }`;
}

function formatPaperResourceLine(
  label: string,
  entry: PaperContextRef,
): string {
  const metadata = [
    `itemId=${entry.itemId}`,
    `contextItemId=${entry.contextItemId}`,
    `citationLabel=${formatPaperCitationLabel(entry)}`,
    `sourceLabel=${formatPaperSourceLabel(entry)}`,
    entry.contentSourceMode ? `sourceMode=${entry.contentSourceMode}` : "",
    entry.citationKey ? `citationKey=${entry.citationKey}` : "",
    entry.mineruCacheDir ? `mineruCacheDir=${entry.mineruCacheDir}` : "",
  ].filter(Boolean);
  return `- ${label}: ${entry.title} [${metadata.join(", ")}]`;
}

function buildPaperResourceRecords(params: {
  group: "selectedPapers" | "fullTextPapers";
  label: string;
  papers?: PaperContextRef[];
}): AgentResourceRecord[] {
  return sortResourceRecords(
    (params.papers || [])
      .filter((entry) =>
        Boolean(
          normalizePositiveInt(entry.itemId) ||
          normalizePositiveInt(entry.contextItemId),
        ),
      )
      .map((entry) => ({
        group: params.group,
        key: paperKey(entry),
        signature: stableJson({
          itemId: normalizePositiveInt(entry.itemId) || 0,
          contextItemId: normalizePositiveInt(entry.contextItemId) || 0,
          title: normalizeText(entry.title, 240),
          attachmentTitle: normalizeText(entry.attachmentTitle, 240),
          citationKey: normalizeText(entry.citationKey, 120),
          firstCreator: normalizeText(entry.firstCreator, 120),
          year: normalizeText(entry.year, 40),
          contentSourceMode: normalizeText(entry.contentSourceMode, 40),
          mineruCacheDir: normalizeText(entry.mineruCacheDir, 1024),
        }),
        line: formatPaperResourceLine(params.label, entry),
      })),
  );
}

function buildCollectionResourceRecords(
  request: AgentRuntimeRequest,
): AgentResourceRecord[] {
  return sortResourceRecords(
    (request.selectedCollectionContexts || [])
      .filter((entry) => normalizePositiveInt(entry.collectionId))
      .map((entry) => ({
        group: "collections" as const,
        key: `${normalizePositiveInt(entry.libraryID) || 0}:${
          normalizePositiveInt(entry.collectionId) || 0
        }`,
        signature: stableJson({
          collectionId: normalizePositiveInt(entry.collectionId) || 0,
          libraryID: normalizePositiveInt(entry.libraryID) || 0,
          name: normalizeText(entry.name, 240),
        }),
        line: `- Collection: ${entry.name} [collectionId=${entry.collectionId}, libraryID=${entry.libraryID}]`,
      })),
  );
}

function tagScopeKey(entry: TagContextRef): string {
  const libraryID = normalizePositiveInt(entry.libraryID) || 0;
  if (entry.scope) {
    return `${libraryID}:scope:${entry.scope}:${
      entry.includeAutomatic ? "auto" : "manual"
    }`;
  }
  return `${libraryID}:tag:${normalizeText(
    entry.normalizedName || entry.name,
    240,
  ).toLowerCase()}`;
}

function formatTagScopeLine(entry: TagContextRef, index: number): string {
  const scopeLabel =
    entry.scope === "allTagged"
      ? "All tagged"
      : entry.scope === "untagged"
        ? "Untagged"
        : "Tag";
  const tagDetail = entry.scope
    ? `tagScope=${entry.scope}`
    : `tag=${entry.name}`;
  const automaticText = entry.includeAutomatic
    ? ", includes automatic tags"
    : "";
  return `- ${scopeLabel} ${index + 1}: ${entry.name} [${tagDetail}, libraryID=${entry.libraryID}${automaticText}]`;
}

function buildTagResourceRecords(
  request: AgentRuntimeRequest,
): AgentResourceRecord[] {
  return sortResourceRecords(
    (request.selectedTagContexts || []).map((entry, index) => ({
      group: "tags" as const,
      key: tagScopeKey(entry),
      signature: stableJson({
        libraryID: normalizePositiveInt(entry.libraryID) || 0,
        name: normalizeText(entry.name, 240),
        normalizedName: normalizeText(entry.normalizedName, 240).toLowerCase(),
        scope: entry.scope || "",
        includeAutomatic: entry.includeAutomatic === true,
      }),
      line: formatTagScopeLine(entry, index),
    })),
  );
}

function buildSelectedTextResourceRecords(
  request: AgentRuntimeRequest,
): AgentResourceRecord[] {
  const selectedTexts = Array.isArray(request.selectedTexts)
    ? request.selectedTexts
    : [];
  return sortResourceRecords(
    selectedTexts.map((text, index) => {
      const source = request.selectedTextSources?.[index] || "unknown";
      const paper = request.selectedTextPaperContexts?.[index];
      const textHash = hashText(text || "");
      const paperPart = paper ? `, paper=${paper.title}` : "";
      return {
        group: "selectedTexts" as const,
        key: `${index}:${source}:${paper ? paperKey(paper) : ""}`,
        signature: stableJson({
          source,
          textHash,
          textLength: text.length,
          paper: paper
            ? {
                itemId: paper.itemId,
                contextItemId: paper.contextItemId,
                title: normalizeText(paper.title, 240),
              }
            : undefined,
        }),
        line: `- Selected text ${index + 1}: source=${source}, chars=${
          text.length
        }, contentHash=${textHash}${paperPart}`,
      };
    }),
  );
}

function buildScreenshotResourceRecords(
  request: AgentRuntimeRequest,
): AgentResourceRecord[] {
  const screenshots = Array.isArray(request.screenshots)
    ? request.screenshots.filter((entry): entry is string => Boolean(entry))
    : [];
  return sortResourceRecords(
    screenshots.map((url, index) => {
      const contentHash = hashText(url);
      return {
        group: "screenshots" as const,
        key: `${index}:${contentHash}`,
        signature: stableJson({
          index,
          contentHash,
        }),
        line: `- Screenshot ${index + 1} [contentHash=${contentHash}]`,
      };
    }),
  );
}

function buildAttachmentResourceRecords(
  request: AgentRuntimeRequest,
): AgentResourceRecord[] {
  const attachments = Array.isArray(request.attachments)
    ? request.attachments
    : [];
  return sortResourceRecords(
    attachments.map((attachment, index) => {
      const key =
        normalizeText(attachment.id, 160) ||
        normalizeText(attachment.contentHash, 160) ||
        `${index}:${normalizeText(attachment.name, 200)}`;
      const contentHash =
        normalizeText(attachment.contentHash, 160) ||
        hashText(
          stableJson({
            name: attachment.name,
            mimeType: attachment.mimeType,
            sizeBytes: attachment.sizeBytes,
            storedPath: attachment.storedPath,
          }),
        );
      const metadata = [
        `id=${attachment.id}`,
        `category=${attachment.category}`,
        attachment.mimeType ? `mimeType=${attachment.mimeType}` : "",
        Number.isFinite(attachment.sizeBytes)
          ? `sizeBytes=${attachment.sizeBytes}`
          : "",
        contentHash ? `contentHash=${contentHash}` : "",
      ].filter(Boolean);
      return {
        group: "attachments" as const,
        key,
        signature: stableJson({
          id: attachment.id,
          name: attachment.name,
          mimeType: attachment.mimeType,
          sizeBytes: attachment.sizeBytes,
          category: attachment.category,
          storedPath: attachment.storedPath,
          contentHash,
        }),
        line: `- Attachment: ${attachment.name} [${metadata.join(", ")}]`,
      };
    }),
  );
}

function formatAttachmentPoolCountSummary(
  counts: AgentAttachmentResourceSummary["attachmentCounts"],
): string {
  const labels: Array<[AgentAttachmentResource["attachmentType"], string]> = [
    ["pdf", "PDF"],
    ["markdown", "MD"],
    ["html", "HTML"],
    ["txt", "TXT"],
    ["docx", "DOCX"],
    ["unsupported", "unsupported"],
  ];
  return labels
    .map(([key, label]) => {
      const count = counts[key] || 0;
      return count ? `${label}=${count}` : "";
    })
    .filter(Boolean)
    .join(", ");
}

function buildAvailableAttachmentResourceLine(
  resource: AgentAttachmentResource,
): string {
  const metadata = [
    `parentItemId=${resource.parentItemId}`,
    `contextItemId=${resource.contextItemId}`,
    `type=${resource.attachmentType}`,
    `readableVia=${resource.readableVia}`,
    resource.contentSourceMode
      ? `sourceMode=${resource.contentSourceMode}`
      : "",
    resource.isPrimary ? "primary=true" : "",
  ].filter(Boolean);
  return `- Available attachment: ${resource.title} under ${resource.parentTitle} [${metadata.join(", ")}]`;
}

function buildAttachmentPoolResourceRecords(
  request: AgentRuntimeRequest,
): AgentResourceRecord[] {
  const records: AgentResourceRecord[] = [];
  for (const resource of request.availableAttachmentResources || []) {
    if (
      !normalizePositiveInt(resource.parentItemId) ||
      !normalizePositiveInt(resource.contextItemId)
    ) {
      continue;
    }
    records.push({
      group: "attachmentPool",
      key: `resource:${resource.parentItemId}:${resource.contextItemId}`,
      signature: stableJson({
        lifecycleState: resource.lifecycleState,
        parentItemId: normalizePositiveInt(resource.parentItemId),
        parentTitle: normalizeText(resource.parentTitle, 240),
        contextItemId: normalizePositiveInt(resource.contextItemId),
        title: normalizeText(resource.title, 240),
        contentType: normalizeText(resource.contentType, 120),
        attachmentType: normalizeText(resource.attachmentType, 40),
        readableVia: normalizeText(resource.readableVia, 40),
        contentSourceMode: normalizeText(resource.contentSourceMode, 40),
        isPrimary: resource.isPrimary === true,
      }),
      line: buildAvailableAttachmentResourceLine(resource),
    });
  }
  for (const summary of request.attachmentResourceSummaries || []) {
    const counts = formatAttachmentPoolCountSummary(summary.attachmentCounts);
    records.push({
      group: "attachmentPool",
      key: `summary:${summary.libraryID}:${summary.collectionId}`,
      signature: stableJson({
        scope: summary.scope,
        collectionId: normalizePositiveInt(summary.collectionId),
        libraryID: normalizePositiveInt(summary.libraryID),
        collectionName: normalizeText(summary.collectionName, 240),
        parentItemCount: normalizePositiveInt(summary.parentItemCount) || 0,
        attachmentCounts: summary.attachmentCounts,
      }),
      line:
        `- Attachment pool summary: ${summary.collectionName} ` +
        `[collectionId=${summary.collectionId}, parentItems=${summary.parentItemCount}${
          counts ? `, ${counts}` : ""
        }]`,
    });
  }
  return sortResourceRecords(records);
}

function buildAgentBaseScopeSnapshot(
  request: AgentRuntimeRequest,
): Record<string, unknown> {
  const activeNote = request.activeNoteContext;
  const metadata = normalizeRecord(request.metadata);
  return {
    conversationKey: normalizePositiveInt(request.conversationKey) || 0,
    libraryID: normalizePositiveInt(request.libraryID) || 0,
    activeItemId: normalizePositiveInt(request.activeItemId) || 0,
    activeNoteId: normalizePositiveInt(activeNote?.noteId) || 0,
    activeNoteKind: normalizeText(activeNote?.noteKind, 40),
    activeNoteParentItemId: normalizePositiveInt(activeNote?.parentItemId) || 0,
    activeNoteTitle: normalizeText(activeNote?.title, 240),
    activeNoteTextHash: activeNote ? hashText(activeNote.noteText || "") : "",
    activeNoteHtmlHash: activeNote?.noteHtml
      ? hashText(activeNote.noteHtml)
      : "",
    scopeType: normalizeText(metadata.scopeType, 80),
    scopeId: normalizeText(metadata.scopeId, 160),
    scopeLabel: normalizeText(metadata.scopeLabel, 240),
  };
}

export function buildAgentResourceSnapshot(
  request: AgentRuntimeRequest,
): AgentResourceSnapshot {
  return {
    baseScope: buildAgentBaseScopeSnapshot(request),
    resources: {
      selectedPapers: buildPaperResourceRecords({
        group: "selectedPapers",
        label: "Selected paper",
        papers: request.selectedPaperContexts,
      }),
      fullTextPapers: buildPaperResourceRecords({
        group: "fullTextPapers",
        label: "Full-text paper",
        papers: request.fullTextPaperContexts,
      }),
      collections: buildCollectionResourceRecords(request),
      tags: buildTagResourceRecords(request),
      selectedTexts: buildSelectedTextResourceRecords(request),
      screenshots: buildScreenshotResourceRecords(request),
      attachments: buildAttachmentResourceRecords(request),
      attachmentPool: buildAttachmentPoolResourceRecords(request),
    },
  };
}

export function buildAgentResourceSignatureFromSnapshot(
  snapshot: AgentResourceSnapshot,
): string {
  return stableJson(snapshot);
}

function flattenAgentResourceSnapshot(
  snapshot: AgentResourceSnapshot,
): AgentResourceRecord[] {
  const records: AgentResourceRecord[] = [];
  for (const group of Object.keys(snapshot.resources) as AgentResourceGroup[]) {
    for (const record of snapshot.resources[group]) {
      records.push({
        ...record,
        key: `${group}:${record.key}`,
      });
    }
  }
  return sortResourceRecords(records);
}

export function diffAgentResourceSnapshots(params: {
  previous: AgentResourceSnapshot;
  current: AgentResourceSnapshot;
}): AgentResourceDelta {
  const previousRecords = new Map(
    flattenAgentResourceSnapshot(params.previous).map((record) => [
      record.key,
      record,
    ]),
  );
  const currentRecords = new Map(
    flattenAgentResourceSnapshot(params.current).map((record) => [
      record.key,
      record,
    ]),
  );
  const added: AgentResourceRecord[] = [];
  const removed: AgentResourceRecord[] = [];
  const changed: AgentResourceRecord[] = [];
  let unchanged = 0;
  for (const [key, current] of currentRecords) {
    const previous = previousRecords.get(key);
    if (!previous) {
      added.push(current);
    } else if (previous.signature !== current.signature) {
      changed.push(current);
    } else {
      unchanged += 1;
    }
  }
  for (const [key, previous] of previousRecords) {
    if (!currentRecords.has(key)) removed.push(previous);
  }
  return {
    added: sortResourceRecords(added),
    removed: sortResourceRecords(removed),
    changed: sortResourceRecords(changed),
    unchanged,
  };
}

export function buildAgentResourceContextPlan(
  request: AgentRuntimeRequest,
): AgentResourceContextPlan {
  const conversationKey = normalizePositiveInt(request.conversationKey) || 0;
  const snapshot = buildAgentResourceSnapshot(request);
  const signature = buildAgentResourceSignatureFromSnapshot(snapshot);
  const priorReadBlock = buildAgentEvidenceContextBlock({
    conversationKey,
    request,
    resourceSignature: signature,
  });
  const stableContextBlock = buildAgentStableResourceContextBlock(request);
  const contextCache = planAgentContextCache({
    request,
    stableContextText: stableContextBlock,
  });
  return {
    conversationKey,
    contextCache,
    resourceSignature: signature,
    stableContextBlock,
    resourceSnapshot: snapshot,
    priorReadBlock,
  };
}

function buildScopeIdentityLines(request: AgentRuntimeRequest): string[] {
  const lines = [
    "Current Zotero context summary:",
    `- Conversation key: ${request.conversationKey}`,
  ];
  if (request.activeItemId) {
    lines.push(`- Active item ID: ${request.activeItemId}`);
  }
  const note = request.activeNoteContext;
  if (note) {
    lines.push(
      `- Active note: ${note.title} [noteId=${note.noteId}, kind=${note.noteKind}]`,
    );
    if (note.parentItemId) {
      lines.push(`- Active note parent item ID: ${note.parentItemId}`);
    }
  }
  return lines;
}

export function buildAgentStableResourceContextBlock(
  request: AgentRuntimeRequest,
): string {
  const fullTextPaperKeySet = new Set(
    (request.fullTextPaperContexts || []).map((entry) => paperKey(entry)),
  );
  const retrievalOnlyPapers = (request.selectedPaperContexts || []).filter(
    (entry) => !fullTextPaperKeySet.has(paperKey(entry)),
  );
  const citationPaperRefs = [
    ...(request.selectedTextPaperContexts || []).filter(
      (entry): entry is PaperContextRef => Boolean(entry),
    ),
    ...retrievalOnlyPapers,
    ...(request.fullTextPaperContexts || []),
  ];
  const lines = [
    "Stable Zotero resource context:",
    ...buildScopeIdentityLines(request),
  ];

  if (citationPaperRefs.length) {
    lines.push(
      BALANCED_EVIDENCE_GUIDANCE,
      "Citation/source label rule: for direct quotes and substantive paper-grounded claims, use the exact sourceLabel shown for the relevant paper.",
      "If quote anchors like [[quote:Q_x7a2]] are provided, use the anchor token for direct quotes instead of manually copying the quote or sourceLabel.",
      "Direct quote text must be copied verbatim in the original source language; never translate quote text to match the user's language. Put any translation outside the blockquote as explanation.",
      "If no quote anchor is provided for a direct quote, keep the source label attached to the quote: put it on the next non-empty line after the blockquote, before any commentary.",
      "Do not write [[source=...]], section=..., or chunk=... metadata in the final answer; those fields are internal context only.",
    );
  }
  if (retrievalOnlyPapers.length) {
    lines.push(
      "Retrieval-only paper refs:",
      ...retrievalOnlyPapers.map((entry) =>
        formatPaperResourceLine("Retrieval paper", entry),
      ),
    );
  }
  if (request.fullTextPaperContexts?.length) {
    lines.push(
      "Full-text paper refs for this turn:",
      ...request.fullTextPaperContexts.map((entry) =>
        formatPaperResourceLine("Full-text paper", entry),
      ),
      ...request.fullTextPaperContexts
        .flatMap((entry) => buildPaperQuoteCitationGuidance(entry))
        .filter(Boolean),
    );
  }
  const attachmentPoolRecords = buildAttachmentPoolResourceRecords(request);
  if (attachmentPoolRecords.length) {
    lines.push(
      "Attachment resource lifecycle:",
      "Primary paper refs above are the default evidence source. Available sibling attachments below are metadata-only until the user explicitly asks for an attachment by name, type, or scope.",
      "For normal summarize, compare, and literature-review requests, keep using primary documents and current MinerU/best-attachment policy. Do not read sibling attachments just because they are listed.",
      "For explicit attachment requests, fuzzy-match within the active parent first, then selected paper or collection scope. Ask only if matches are genuinely tied or risky.",
      "Use read_attachment for Markdown/HTML/TXT/DOCX attachments, paper_read for PDF attachments, and report unsupported formats as visible but not directly readable.",
      "Available attachment resources:",
      ...attachmentPoolRecords.map((record) => record.line),
    );
  }
  if (request.selectedCollectionContexts?.length) {
    lines.push(
      "Selected Zotero collection scopes:",
      ...request.selectedCollectionContexts.map(
        (entry, index) =>
          `- Collection ${index + 1}: ${entry.name} [collectionId=${entry.collectionId}, libraryID=${entry.libraryID}]`,
      ),
      "Treat collection membership as the scope boundary. Use library_retrieve({ scope:{ collectionIds:[<collectionId>] }, query:'...', queryVariants:[...], intent:'enumerate'|'summarize', depth:'metadata'|'evidence' }) for broad comprehensive evidence search when variants would improve recall, library_search({ entity:'items', mode:'list', filters:{ collectionId:<collectionId> } }) for catalog listing, or collection-scoped actions when the user asks to operate on them. Do not assume all full text has already been read.",
      "When reading papers from a collection, prefer library_retrieve for staged resource-pool search. It maps metadata, scans indexed/searchable text, returns a paper-level frontier, and expands snippets only for selected branches; use paper_read only for close reading explicit itemId/contextItemId targets returned by search/retrieval. Do not use the active reader paper as an implicit collection member.",
      "If the user explicitly asks to read or analyze the full text of every paper in a collection, use library_retrieve or plan a batch workflow: enumerate papers, read/process them in bounded batches, create compact per-paper digests with evidence, then synthesize.",
      "If the user explicitly asks to include or only read child attachments in this collection, enumerate item attachments with library_search plus library_read sections:['attachments']; otherwise ignore sibling attachments for primary-document workflows.",
    );
  }
  if (request.selectedTagContexts?.length) {
    lines.push(
      "Selected Zotero tag scopes:",
      ...request.selectedTagContexts.map((entry, index) =>
        formatTagScopeLine(entry, index),
      ),
      "Treat tag membership as the scope boundary. Use library_retrieve({ query:'...', queryVariants:[...], intent:'enumerate'|'summarize', depth:'metadata'|'evidence' }) to search the selected tag pool by default, library_retrieve({ scope:{ tagNames:['<tag>'] }, query:'...', intent:'enumerate' }) for an explicit named tag scope, or library_search({ entity:'items', mode:'list', filters:{ tag:'<tag>' } }) for catalog listing. Do not ask which tag the user means when a selected tag scope is listed here.",
      "When reading papers from a tag, prefer library_retrieve for staged resource-pool search. It maps metadata, scans indexed/searchable text inside the tag where possible, returns a paper-level frontier, and expands snippets only for selected branches; use paper_read only for close reading explicit itemId/contextItemId targets returned by search/retrieval. Do not use the active reader paper as an implicit tag member.",
      "If the user explicitly asks to analyze the full text of every paper in a tag, use library_retrieve or plan a bounded batch workflow: enumerate papers, read/process them in batches, create compact per-paper digests with evidence, then synthesize.",
    );
  }

  const resourceRecords = [
    ...buildAttachmentResourceRecords(request),
    ...buildScreenshotResourceRecords(request),
  ];
  if (resourceRecords.length) {
    lines.push(
      "Current attached visual/file resources:",
      ...resourceRecords.map((record) => record.line),
    );
  }

  return lines.filter(Boolean).join("\n");
}

export function commitAgentReadActivities(params: {
  conversationKey: number;
  activities: AgentPendingReadActivity[];
  resourceSignature?: string;
}): Promise<void> {
  return commitAgentCacheEvidenceActivities(params);
}

export function clearAgentReadLedger(): void {
  clearAgentEvidenceCache();
}

export function buildAgentPriorReadContextBlock(params: {
  conversationKey: number;
  request?: AgentRuntimeRequest;
  resourceSignature?: string;
}): string {
  return buildAgentEvidenceContextBlock(params);
}
