import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { createHash } from "node:crypto";
import { dirname, join } from "node:path";

const run = promisify(execFile);

// `crtr -h` is the auto-loaded tool guide. Its body leads with a <runtime-state>
// block (loaded skills + live workers) that changes during a session, so we
// refresh on a short TTL rather than caching once — stale worker/skill state
// would defeat the point of surfacing it. The whole guide is wrapped in one
// outer <crtr-tool-guide> domain; nesting stays at two levels (guide → state).
const TTL_MS = 15_000;

// The TTL cache is also persisted to disk, keyed by cwd (skills resolve by
// scope, so the guide is cwd-specific). Every broker/node is a FRESH process, so
// the in-memory cache alone is always cold at session_start — each boot would
// re-spawn the ~0.5s `crtr -h`. The disk layer lets a cluster of node boots
// within one TTL window (daemon revives, child spawns, rapid relaunches) share a
// single capture, while preserving the 15s freshness contract on runtime-state.
function helpCacheFile(cwd: string): string {
  const h = createHash("sha1").update(cwd).digest("hex").slice(0, 16);
  return join(homedir(), ".crouter", "cache", `crouter-help-${h}.json`);
}

export default function (pi: ExtensionAPI) {
  let helpText = "";
  let capturedAt = 0;

  async function refresh(): Promise<void> {
    if (Date.now() - capturedAt < TTL_MS && helpText) return; // in-memory fresh
    const file = helpCacheFile(process.cwd());

    // Cross-process disk cache: reuse a recent capture from a sibling boot.
    try {
      const c = JSON.parse(readFileSync(file, "utf8")) as { text?: string; at?: number };
      if (typeof c.text === "string" && c.text && typeof c.at === "number" && Date.now() - c.at < TTL_MS) {
        helpText = c.text;
        capturedAt = c.at;
        return; // fresh on disk — skip the spawn
      }
    } catch {
      // miss / corrupt — fall through to re-probe
    }

    try {
      const { stdout } = await run("crouter", ["-h"], { timeout: 10_000 });
      helpText = stdout.trim();
      capturedAt = Date.now();
      try {
        mkdirSync(dirname(file), { recursive: true });
        writeFileSync(file, JSON.stringify({ text: helpText, at: capturedAt }), "utf8");
      } catch {
        // best-effort: a missing cache just means the next boot re-probes
      }
    } catch {
      // soft-fail: keep last-known text (or empty); never block the turn
    }
  }

  pi.on("session_start", refresh);

  pi.on("before_agent_start", async (event) => {
    await refresh();
    if (!helpText) return;

    const block = `<crtr-tool-guide>\n${helpText}\n</crtr-tool-guide>`;

    // Place the guide in the tool-selection frame, right after pi's native
    // "Available tools" list rather than at the very bottom — the agent decides
    // which capability to reach for while reading the tools, so the crtr
    // commands (agent/skill/job) must be present there, not 3k tokens later.
    // `\n\nGuidelines:` is the stable seam that closes the tools area. Fall back
    // to appending if pi ever changes that marker.
    const anchor = "\n\nGuidelines:";
    const idx = event.systemPrompt.indexOf(anchor);
    if (idx === -1) {
      return { systemPrompt: `${event.systemPrompt}\n\n${block}` };
    }
    return {
      systemPrompt: `${event.systemPrompt.slice(0, idx)}\n\n${block}${event.systemPrompt.slice(idx)}`,
    };
  });
}
