#!/usr/bin/env bun
// Claude Agent lifecycle -> Overdeck journal.  This hook is deliberately
// fail-soft: observability must never delay or change an Agent tool call.

import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";

function readInput() {
  try {
    return JSON.parse(readFileSync(0, "utf8") || "{}");
  } catch {
    return null;
  }
}

function valueOrAbsent(value, reason) {
  const text = typeof value === "string" ? value.trim() : "";
  return text || `ABSENT (${reason})`;
}

function responseAgentId(response) {
  if (response && typeof response === "object" && typeof response.agentId === "string") {
    return response.agentId.trim();
  }
  try {
    const match = String(response ?? "").match(/agentId["']?\s*[:=]\s*["']?([A-Za-z0-9_-]+)/);
    return match?.[1] ?? "";
  } catch {
    return "";
  }
}

function journalFile() {
  // The override is test-only; production follows collector/src/paths.ts.
  return process.env.OVERDECK_JOURNAL_FILE || join(
    process.env.OVERDECK_STATE_DIR || join(homedir(), ".local", "state", "overdeck"),
    "items.jsonl",
  );
}

function publish(row) {
  try {
    const file = journalFile();
    mkdirSync(dirname(file), { recursive: true });
    appendFileSync(file, `${JSON.stringify(row)}\n`, "utf8");
  } catch {
    // Journal availability must not affect Claude's tool lifecycle.
  }
}

function stateFor(hasAbsentData) {
  return hasAbsentData
    ? { severity: "warn", kind: "alert" }
    : { severity: "info", kind: "progress" };
}

function spawn(payload) {
  if (String(payload.tool_name) !== "Agent") return;
  const input = payload.tool_input && typeof payload.tool_input === "object" ? payload.tool_input : {};
  const agentId = responseAgentId(payload.tool_response);
  const sessionId = valueOrAbsent(payload.session_id, "hook payload omitted session id");
  const entity = agentId || `ABSENT:${sessionId}`;
  const rawDescription = typeof input.description === "string" ? input.description.trim() : "";
  const rawType = typeof input.subagent_type === "string" ? input.subagent_type.trim() : "";
  const description = valueOrAbsent(rawDescription, "Agent input omitted description");
  const type = valueOrAbsent(rawType, "Agent input omitted subagent type");
  const state = stateFor(!agentId || !rawDescription || !rawType);
  publish({
    id: `claude-subagent:${entity}`,
    source: "claude-subagent",
    ...state,
    title: "Claude subagent RUNNING",
    detail: `agent id: ${valueOrAbsent(agentId, "Agent response omitted agent id")}; description: ${description}; subagent type: ${type}`,
  });
}

function finish(payload) {
  const agentId = String(payload.agent_id ?? payload.agentId ?? "").trim();
  const sessionId = valueOrAbsent(payload.session_id, "hook payload omitted session id");
  const entity = agentId || `ABSENT:${sessionId}`;
  const rawType = String(payload.agent_type ?? payload.subagent_type ?? "").trim();
  const rawReason = String(payload.stop_reason ?? payload.reason ?? "").trim();
  const type = valueOrAbsent(rawType, "hook payload omitted subagent type");
  const reason = valueOrAbsent(rawReason, "hook payload omitted completion reason");
  const state = stateFor(!agentId || !rawType || !rawReason);
  publish({
    id: `claude-subagent:${entity}`,
    source: "claude-subagent",
    ...state,
    title: "Claude subagent FINISHED",
    detail: `agent id: ${valueOrAbsent(agentId, "SubagentStop payload omitted agent id")}; subagent type: ${type}; completion: ${reason}`,
  });
}

try {
  const payload = readInput();
  if (payload && (String(payload.hook_event_name) === "SubagentStop" || (payload.agent_id && String(payload.tool_name) !== "Agent"))) finish(payload);
  else if (payload) spawn(payload);
} catch {
  // Defensive final boundary for future Claude payload changes.
}
process.exit(0);
