// src/layer/ReadCacheLayer.ts
import type { Layer, SavingEvent, SessionId, FileHash } from "./types.ts";
import type { ReadCache } from "../cache/ReadCache.ts";
import type { Provider } from "../provider/types.ts";
import { fingerprintContent } from "../cache/fingerprint.ts";
import { formatElision } from "./Elision.ts";

export interface ReadCacheLayerOpts {
  cache: ReadCache;
  sessionId: SessionId;
  provider: Provider;
}

export function makeReadCacheLayer(opts: ReadCacheLayerOpts): Layer {
  const { cache, provider } = opts;
  let turn = 0;
  const firstSeenTurn = new Map<string, number>();
  const events: SavingEvent[] = [];

  return {
    id: "read-cache",
    init(): void {
      turn = 0;
      firstSeenTurn.clear();
      events.length = 0;
    },
    outbound(body: unknown): unknown {
      turn += 1;
      // Use provider to extract tool results (canonical, provider-agnostic)
      const parsed = provider.parseRequest(body, { kind: "messages", isStreaming: false });
      const results = provider.extractLastTurnToolResults(parsed);
      let out = body;
      for (const r of results) {
        // Only elide Read tool results (file reads). Bash/MCP outputs must never be elided.
        if (r.toolName !== "Read") continue;
        const path = typeof r.toolInput?.["file_path"] === "string"
          ? (r.toolInput["file_path"] as string)
          : "";
        const hash = fingerprintContent(r.content);
        const prev = firstSeenTurn.get(hash);
        if (prev != null) {
          const sigil = formatElision({ hash: hash as FileHash, firstSeenTurn: prev, path });
          const beforeBytes = Buffer.byteLength(r.content, "utf8");
          const afterBytes = Buffer.byteLength(sigil, "utf8");
          events.push({
            layerId: "read-cache",
            kind: "elision",
            rawBytes: beforeBytes,
            sentBytes: afterBytes,
          });
          out = provider.rewriteToolResult(out, r.toolCallId, sigil);
        } else {
          firstSeenTurn.set(hash, turn);
          cache.store({ path, content: r.content });
        }
      }
      return out;
    },
    inbound(body: unknown): unknown {
      return body;
    },
    observe(): SavingEvent[] {
      const drained = [...events];
      events.length = 0;
      return drained;
    },
    dispose(): void {
      firstSeenTurn.clear();
      events.length = 0;
    },
  };
}
