/**
 * src/http/hook.ts (H2) — map a Claude Code hook POST to a session status update.
 *
 * Pure + never throws (SEC-M7). The web-terminal sessionId arrives in the
 * `X-Webterm-Session` header (injected as $WEBTERM_SESSION on spawn); the hook
 * payload arrives as JSON in the body. We translate the event into a coarse
 * ClaudeStatus the UI can show. The server is still a byte-shuttle for the
 * terminal stream — this is an out-of-band side-channel.
 *
 * T-hook-intake (v0.7): parseHookEvent now returns HookEventFull, which adds
 * `at` (server timestamp), `eventClass` (raw hook name), `toolInput` (raw
 * passthrough), and `gate` ('plan' | 'tool') for permission-mode relay (B4).
 */

import type { ClaudeStatus, PermissionGate } from '../types.js';

/** Base shape kept for backward compat; new callers should use HookEventFull. */
export interface HookEvent {
  sessionId: string;
  status: ClaudeStatus;
  detail?: string;
}

/**
 * Full event shape returned by parseHookEvent (A4 / B4).
 *
 * `at`         — server Date.now() at parse time (timeline anchor).
 * `eventClass` — the raw hook_event_name from the whitelist, passed as-is
 *                so callers (timeline, manager) can make semantic decisions
 *                without re-parsing.
 * `toolInput`  — raw tool_input value from the JSON body, unknown type,
 *                passed through verbatim for timeline / future use.
 * `gate`       — 'plan' for an ExitPlanMode PermissionRequest (three-way
 *                approve / auto / keep-planning); 'tool' for any other
 *                PermissionRequest; absent for all other events.
 */
export interface HookEventFull extends HookEvent {
  at: number;
  eventClass: string;
  toolInput?: unknown;
  gate?: PermissionGate;
}

/** Whitelist of hook_event_name values we recognise. Unknown names → null. */
const ALLOWED_EVENTS = new Set([
  'PreToolUse',
  'PostToolUse',
  'UserPromptSubmit',
  'PermissionRequest',
  'Stop',
  'SessionEnd',
  'Notification',
]);

/**
 * Parse a Claude Code hook POST into a HookEventFull.
 *
 * @param sessionId  Value of the `X-Webterm-Session` header.
 * @param body       Parsed JSON body of the hook POST (untrusted, unknown).
 * @returns          A HookEventFull, or null when the payload is unusable.
 *                   Never throws (SEC-M7: unknown + narrowing).
 */
export function parseHookEvent(
  sessionId: string | undefined,
  body: unknown,
): HookEventFull | null {
  if (typeof sessionId !== 'string' || sessionId.length === 0) return null;
  if (body === null || typeof body !== 'object' || Array.isArray(body)) return null;

  const b = body as Record<string, unknown>;
  const event = typeof b['hook_event_name'] === 'string' ? b['hook_event_name'] : '';

  if (!ALLOWED_EVENTS.has(event)) return null;

  const notif = typeof b['notification_type'] === 'string' ? b['notification_type'] : '';
  const tool = typeof b['tool_name'] === 'string' ? b['tool_name'] : undefined;

  let status: ClaudeStatus;
  switch (event) {
    case 'PreToolUse':
    case 'PostToolUse':
    case 'UserPromptSubmit':
      status = 'working';
      break;
    case 'PermissionRequest':
      status = 'waiting';
      break;
    case 'Stop':
    case 'SessionEnd':
      status = 'idle';
      break;
    case 'Notification':
      status = notif === 'permission_prompt' ? 'waiting' : 'idle';
      break;
    default:
      // Unreachable: ALLOWED_EVENTS guard above catches unknown names.
      return null;
  }

  // detail: only when waiting + a tool name is present.
  const detail = status === 'waiting' && tool !== undefined ? tool : undefined;

  // toolInput: pass through verbatim when the key is present (even if null).
  const hasToolInput = 'tool_input' in b;
  const toolInput = hasToolInput ? b['tool_input'] : undefined;

  // gate: classify the permission gate type (B4).
  const gate: PermissionGate | undefined =
    event === 'PermissionRequest'
      ? tool === 'ExitPlanMode'
        ? 'plan'
        : 'tool'
      : undefined;

  return {
    sessionId,
    status,
    at: Date.now(),
    eventClass: event,
    ...(detail !== undefined ? { detail } : {}),
    ...(hasToolInput ? { toolInput } : {}),
    ...(gate !== undefined ? { gate } : {}),
  };
}
