/**
 * normalize-agent-status (#10) — clean-room vendor-event → bucket mapping.
 *
 * Different coding agents (claude, opencode, codex/gemini, goose, qwen) emit
 * lifecycle hook events under inconsistent names: PascalCase (`SessionStart`),
 * snake_case (`session_start`), camelCase (`sessionStart`), and a handful of
 * provider-specific approval events (`exec_approval_request`). This module
 * collapses every known event name into one of three coarse signals:
 *
 *   working  — the agent is actively progressing a turn
 *   blocked  — the agent is waiting on a human (permission / approval / question)
 *   done     — the turn / session ended cleanly
 *
 * `null` is returned for anything unrecognized so callers can ignore noise.
 *
 * Built now for the scoped status-publish, but specifically shaped for reuse by
 * the documented config-injection follow-on: a future notify-hook injected into
 * each agent's native config will POST the RAW vendor event name to a BooCoder
 * endpoint, which runs this helper to derive the normalized status. The names
 * below are facts about each agent's hook surface — not copied vendor code.
 */

export type AgentStatus = 'working' | 'blocked' | 'idle' | 'error';

/** The coarse signal a raw vendor event collapses to. */
export type AgentEventBucket = 'working' | 'blocked' | 'done';

// Each bucket lists the canonical vendor event names. Lookup is
// case-insensitive AND separator-insensitive (snake_case / camelCase /
// PascalCase all fold to the same key), so we normalize the raw input the same
// way before matching rather than enumerating every spelling here.
const WORKING_EVENTS = [
  'SessionStart',
  'UserPromptSubmit',
  'UserPromptSubmitted',
  'PostToolUse',
  'PostToolUseFailure',
  'BeforeAgent',
  'AfterTool',
  'task_started',
] as const;

const BLOCKED_EVENTS = [
  'PreToolUse',
  'Notification',
  'PermissionRequest',
  'exec_approval_request',
  'apply_patch_approval_request',
  'request_user_input',
] as const;

const DONE_EVENTS = [
  'Stop',
  'AfterAgent',
  'SessionEnd',
  'task_complete',
  'agent-turn-complete',
] as const;

/**
 * Fold a raw event name to a separator/case-insensitive key:
 * strip every non-alphanumeric character and lowercase. So `post_tool_use`,
 * `postToolUse`, `PostToolUse`, and `POST-TOOL-USE` all map to `posttooluse`.
 */
function foldKey(raw: string): string {
  return raw.replace(/[^a-z0-9]/gi, '').toLowerCase();
}

function buildLookup(
  groups: ReadonlyArray<readonly [AgentEventBucket, readonly string[]]>,
): Map<string, AgentEventBucket> {
  const map = new Map<string, AgentEventBucket>();
  for (const [bucket, names] of groups) {
    for (const name of names) map.set(foldKey(name), bucket);
  }
  return map;
}

const EVENT_LOOKUP = buildLookup([
  ['working', WORKING_EVENTS],
  ['blocked', BLOCKED_EVENTS],
  ['done', DONE_EVENTS],
]);

/**
 * Map a raw vendor hook-event name to its normalized bucket, or `null` when the
 * name is unknown / undefined. Case- and separator-insensitive.
 */
export function normalizeAgentEvent(raw: string | undefined): AgentEventBucket | null {
  if (!raw) return null;
  return EVENT_LOOKUP.get(foldKey(raw)) ?? null;
}
