// Per-agent telemetry + hook configuration for CTM.
// Single source of truth for:
//   - which env vars enable OTLP export (set on pty.spawn)
//   - which hooks need to be installed in which config files
//   - agent detection (command matching)
// Port of /tmp/clideck/agent-presets.json, adapted for CTM and the CTM_PORT env var.

/**
 * @typedef {Object} AgentPreset
 * @property {string}  presetId      - stable identifier ('claude-code', 'codex', 'gemini-cli')
 * @property {string}  name          - display name for UI
 * @property {RegExp}  commandRegex  - matches the cmd string used by detectAgentType
 * @property {Object}  [telemetryEnv] - env vars to set when spawning this agent. Values may
 *                                      contain "{{port}}" which will be replaced at spawn
 *                                      time with the CTM HTTP port.
 * @property {string}  [configPath]  - path to the agent's user config file (for hook install/status context)
 * @property {'json' | 'toml'} [configFormat] - format of the config file (informs the patcher)
 * @property {'hooks' | 'terminal'} [liveStatusMode] - how CTM gets live status for this runtime
 * @property {Array<{ kind: string, matcher: string|null, bin: string, route: string }>} [hooks]
 *   Hooks to install into the agent's config.
 *     kind    — agent-specific event name ("Stop", "SessionStart", etc.)
 *     matcher — optional tool matcher (Claude Code only, e.g. ".*" for all tools)
 *     bin     — which CTM hook script to call ("claude-hook", "gemini-hook", "codex-notify")
 *     route   — the path suffix the hook POSTs to ("/hook/claude/stop" etc.)
 */

/** @type {Record<string, AgentPreset>} */
const PRESETS = {
  'claude-code': {
    presetId: 'claude-code',
    name: 'Claude Code',
    commandRegex: /(^|\/)claude(\s|$)/,
    // CTM's state signal for Claude comes primarily from hooks; the OTEL log
    // records are useful for session-ID capture (via session.id attribute).
    telemetryEnv: {
      CLAUDE_CODE_ENABLE_TELEMETRY: '1',
      OTEL_LOGS_EXPORTER: 'otlp',
      OTEL_EXPORTER_OTLP_PROTOCOL: 'http/json',
      OTEL_EXPORTER_OTLP_ENDPOINT: 'http://localhost:{{port}}',
      OTEL_LOGS_EXPORT_INTERVAL: '2000',
    },
    configPath: '~/.claude/settings.json',
    configFormat: 'json',
    liveStatusMode: 'hooks',
    hooks: [
      { kind: 'SessionStart',     matcher: null, bin: 'claude-hook', route: 'start' },
      { kind: 'UserPromptSubmit', matcher: null, bin: 'claude-hook', route: 'start' },
      { kind: 'Stop',             matcher: null, bin: 'claude-hook', route: 'stop'  },
      { kind: 'Notification',     matcher: null, bin: 'claude-hook', route: 'idle'  },
      { kind: 'PreToolUse',       matcher: '.*', bin: 'claude-hook', route: 'menu'  },
    ],
  },
  'codex': {
    presetId: 'codex',
    name: 'Codex',
    commandRegex: /(^|\/)codex(\s|$)/,
    // Codex drives state primarily from its OTEL log records + a "notify" hook
    // fired when the turn is complete.
    telemetryEnv: {
      OTEL_LOGS_EXPORTER: 'otlp',
      OTEL_EXPORTER_OTLP_PROTOCOL: 'http/json',
      OTEL_EXPORTER_OTLP_ENDPOINT: 'http://localhost:{{port}}',
      OTEL_LOGS_EXPORT_INTERVAL: '2000',
    },
    configPath: '~/.codex/config.toml',
    configFormat: 'toml',
    liveStatusMode: 'hooks',
    hooks: [
      // [notify] block handler; patcher writes this differently than Claude's hooks.
      { kind: 'notify', matcher: null, bin: 'codex-notify', route: 'stop' },
    ],
  },
  'gemini-cli': {
    presetId: 'gemini-cli',
    name: 'Gemini CLI',
    commandRegex: /(^|\/)gemini(\s|$)/,
    // Gemini drops OTEL_RESOURCE_ATTRIBUTES so we can't correlate via env;
    // instead the hook scripts propagate CLIDECK_SESSION_ID-style ID themselves.
    // No OTEL env — state flows through hooks only.
    configPath: '~/.gemini/settings.json',
    configFormat: 'json',
    liveStatusMode: 'hooks',
    hooks: [
      { kind: 'BeforeAgent', matcher: null, bin: 'gemini-hook', route: 'start' },
      { kind: 'AfterAgent',  matcher: null, bin: 'gemini-hook', route: 'stop'  },
      { kind: 'SessionEnd',  matcher: null, bin: 'gemini-hook', route: 'stop'  },
      { kind: 'BeforeTool',  matcher: null, bin: 'gemini-hook', route: 'menu'  },
    ],
  },
  'cursor': {
    presetId: 'cursor',
    name: 'Cursor',
    commandRegex: /(^|\/)cursor-agent(\s|$)/,
    // Cursor Agent CLI does not currently expose a documented settings hook
    // contract like Claude/Codex/Gemini. CTM still owns reliable live status
    // for Cursor sessions it launches through the managed PTY lifecycle.
    configPath: '~/.cursor/cli-config.json',
    configFormat: 'json',
    liveStatusMode: 'terminal',
    hooks: [],
  },
};

/** Detect which preset applies to a given spawn command (same contract as server.js's detectAgentType). */
function detectPresetId(cmd) {
  if (!cmd) return null;
  const s = String(cmd);
  for (const [id, p] of Object.entries(PRESETS)) {
    if (p.commandRegex.test(s) || s.includes(p.presetId === 'claude-code' ? 'claude' : p.presetId === 'gemini-cli' ? 'gemini' : p.presetId)) {
      return id;
    }
  }
  return null;
}

function getPreset(presetId) {
  return PRESETS[presetId] || null;
}

const INHERITED_AGENT_ENV_KEYS = [
  'CTM_SESSION_ID',
  'CCMANAGER_SESSION_ID',
  'CLIDECK_SESSION_ID',
  'CLAUDECODE',
  'CLAUDE_CODE',
  'CLAUDE_CODE_ENTRYPOINT',
  'CLAUDE_CODE_ENABLE_TELEMETRY',
  'CODEX_THREAD_ID',
  'CODEX_SESSION_ID',
  'CODEX_TURN_ID',
  'CODEX_CI',
  'CODEX_SANDBOX_NETWORK_DISABLED',
  'GEMINI_SESSION_ID',
];

function sanitizeInheritedAgentEnv(env) {
  if (!env || typeof env !== 'object') return env;
  for (const key of INHERITED_AGENT_ENV_KEYS) delete env[key];
  return env;
}

function buildResourceAttributes(existing, ctmSessionId) {
  const prior = String(existing || '')
    .split(',')
    .map(s => s.trim())
    .filter(Boolean)
    .filter(s => !/^ctm\.session_id=/.test(s) && !/^clideck\.session_id=/.test(s));
  prior.push(`ctm.session_id=${ctmSessionId}`);
  return prior.join(',');
}

/**
 * Build the env object to inject at pty.spawn time.
 *   - ctmSessionId: CTM's UUID for the session (goes into OTEL_RESOURCE_ATTRIBUTES)
 *   - port:         CTM's HTTP port (substituted into OTEL endpoint URLs)
 *   - cmd:          the command being spawned (for preset detection)
 * Returns an object that can be spread into the env option: `env: { ...process.env, ...telemetryEnv }`.
 * Always includes CTM_SESSION_ID and CTM_PORT — hook bins read these from their own env.
 */
function buildTelemetryEnv(ctmSessionId, cmd, port, baseEnv = process.env) {
  const presetId = detectPresetId(cmd);
  const preset = presetId ? PRESETS[presetId] : null;
  const env = {
    CTM_SESSION_ID: ctmSessionId,
    CTM_PORT: String(port),
    OTEL_RESOURCE_ATTRIBUTES: buildResourceAttributes(baseEnv?.OTEL_RESOURCE_ATTRIBUTES, ctmSessionId),
  };
  if (!preset || !preset.telemetryEnv) return env;
  for (const [k, v] of Object.entries(preset.telemetryEnv)) {
    env[k] = String(v).replace('{{port}}', String(port));
  }
  return env;
}

module.exports = {
  PRESETS,
  detectPresetId,
  getPreset,
  buildTelemetryEnv,
  sanitizeInheritedAgentEnv,
};
