import { HOOK_CONFIG } from './hook-config.generated.js';
import type { SymbiontRegistration } from '../symbionts/manifest-schema.js';
import { symbiontHasCapability } from '../symbionts/capabilities.js';

export interface HookResponse {
  additionalContext?: string;
  /**
   * Multiple distinct injection blocks, in display order. Used by
   * symbionts whose hook response format supports rendering each as
   * its own visible event (currently Antigravity, where they map to
   * separate `injectSteps[]` entries — the agent and the user see
   * them as separate cards instead of one concatenated block).
   *
   * Format-level fallback: agents whose response shape carries only
   * a single block (plain-text) join the entries with a blank line.
   * When both fields are populated, `additionalSteps` wins for
   * formats that support it; `additionalContext` is the single-block
   * legacy field other paths still consume.
   */
  additionalSteps?: string[];
  continue?: boolean;
  stopReason?: string;
  userMessage?: string;
  followupMessage?: string;
  systemMessage?: string;
}

type SemanticField = keyof HookResponse;

export function writeHookResponse(
  symbiont: string | undefined,
  hookEvent: string,
  response: HookResponse = {},
): void {
  if (hookEvent === 'pre-tool-use' && symbiontHasCapability(symbiont, 'preToolUseInjection')) {
    process.stdout.write(serializePreToolUseEnvelope(response));
    return;
  }
  if (hookEvent === 'subagent-start') {
    if (symbiontHasCapability(symbiont, 'subagentStartInjection')) {
      process.stdout.write(serializeSubagentStartEnvelope(symbiont, response));
    }
    return;
  }

  const config = resolveHookResponseConfig(symbiont);
  switch (config.format) {
    case 'json':
      process.stdout.write(serializeJson(response, config.fieldNames ?? {}));
      return;
    case 'plain-text': {
      // Plain-text format carries only a single block. Prefer
      // additionalSteps when present (joining with a blank line so
      // boundaries stay visible); fall back to the single-string
      // legacy field.
      const text = response.additionalSteps && response.additionalSteps.length > 0
        ? response.additionalSteps.filter((s) => s && s.length > 0).join('\n\n')
        : response.additionalContext;
      if (text) process.stdout.write(text);
      return;
    }
    case 'antigravity-inject-steps':
      process.stdout.write(serializeAntigravityResponse(hookEvent, response));
      return;
  }
  void hookEvent;
}

/**
 * Antigravity response shapes per https://antigravity.google/docs/hooks:
 *   - PreInvocation: `{ injectSteps: [{ userMessage: "<text>" }] }` (or `{}` when nothing to inject)
 *   - PostToolUse:   `{}`
 *   - Stop:          `{ decision: "continue" | "allow" }` — `decision` is required; `"continue"` force-continues.
 *
 * `userMessage` is the persistent injection form; `ephemeralMessage` is silently
 * dropped from the model's trajectory in the AGY CLI surface.
 */
function serializeAntigravityResponse(hookEvent: string, response: HookResponse): string {
  if (hookEvent === 'stop') {
    return JSON.stringify({ decision: 'allow' });
  }
  if (hookEvent === 'session-start' || hookEvent === 'user-prompt-submit') {
    // Prefer per-block additionalSteps so distinct payloads (e.g.
    // Cortex instructions vs prompt-time spores) render as separate
    // injectSteps in the Antigravity UI. Falls back to the legacy
    // single-block additionalContext for callers that haven't
    // adopted the new field.
    const blocks =
      response.additionalSteps && response.additionalSteps.length > 0
        ? response.additionalSteps.filter((s) => s && s.length > 0)
        : response.additionalContext
          ? [response.additionalContext]
          : [];
    if (blocks.length === 0) return '{}';
    return JSON.stringify({
      injectSteps: blocks.map((text) => ({ userMessage: text })),
    });
  }
  return '{}';
}

type HookResponseConfig = NonNullable<SymbiontRegistration['hookResponse']>;

const DEFAULT_CONFIG: HookResponseConfig = { format: 'plain-text' };

function resolveHookResponseConfig(symbiont: string | undefined): HookResponseConfig {
  if (!symbiont) return DEFAULT_CONFIG;
  return HOOK_CONFIG[symbiont]?.hookResponse ?? DEFAULT_CONFIG;
}

function serializeJson(
  response: HookResponse,
  fieldNames: Record<string, string>,
): string {
  const body: Record<string, unknown> = {};
  for (const [semantic, value] of Object.entries(response) as Array<[SemanticField, unknown]>) {
    if (value === undefined) continue;
    const wire = fieldNames[semantic];
    if (!wire) continue;
    body[wire] = value;
  }
  return JSON.stringify(body);
}

function serializePreToolUseEnvelope(response: HookResponse): string {
  if (!response.additionalContext) return '';
  return JSON.stringify({
    hookSpecificOutput: {
      hookEventName: 'PreToolUse',
      additionalContext: response.additionalContext,
    },
  });
}

function serializeSubagentStartEnvelope(
  symbiont: string | undefined,
  response: HookResponse,
): string {
  if (!response.additionalContext) return '';
  if (symbiont === 'copilot') {
    return JSON.stringify({ additionalContext: response.additionalContext });
  }
  return JSON.stringify({
    hookSpecificOutput: {
      hookEventName: 'SubagentStart',
      additionalContext: response.additionalContext,
    },
  });
}
