/**
 * HooksRegistry —— 注册 / 触发生命周期事件 hooks。
 *
 * fail-soft：hook 抛错只写 warn log 不阻塞主流程。
 * PreToolUse 例外：hook 显式返回 { reject: true } 则阻断工具调用。
 */

import { Injectable, Logger } from '@nestjs/common';
import type {
  HookEvent,
  OnTurnStartHook,
  PreToolUseHook,
  PreToolUseResult,
  PostToolUseHook,
  OnAssistantTextHook,
  OnAssistantTextResult,
  PostCompactHook,
  OnTurnEndHook,
  OnTurnStartContext,
  PreToolUseContext,
  PostToolUseContext,
  OnAssistantTextContext,
  PostCompactContext,
  OnTurnEndContext,
} from './hooks.types';

interface RegisteredHook {
  registeredBy: string;
  /** 数值越小越先跑 */
  priority: number;
  fn: unknown;
}

@Injectable()
export class HooksRegistry {
  private readonly logger = new Logger(HooksRegistry.name);
  private readonly hooks: Record<HookEvent, RegisteredHook[]> = {
    on_turn_start: [],
    pre_tool_use: [],
    post_tool_use: [],
    on_assistant_text: [],
    post_compact: [],
    on_turn_end: [],
  };

  // ─────────── 注册 ───────────

  registerOnTurnStart(registeredBy: string, fn: OnTurnStartHook, priority = 100): void {
    this.add('on_turn_start', registeredBy, fn, priority);
  }
  registerPreToolUse(registeredBy: string, fn: PreToolUseHook, priority = 100): void {
    this.add('pre_tool_use', registeredBy, fn, priority);
  }
  registerPostToolUse(registeredBy: string, fn: PostToolUseHook, priority = 100): void {
    this.add('post_tool_use', registeredBy, fn, priority);
  }
  registerOnAssistantText(registeredBy: string, fn: OnAssistantTextHook, priority = 100): void {
    this.add('on_assistant_text', registeredBy, fn, priority);
  }
  registerPostCompact(registeredBy: string, fn: PostCompactHook, priority = 100): void {
    this.add('post_compact', registeredBy, fn, priority);
  }
  registerOnTurnEnd(registeredBy: string, fn: OnTurnEndHook, priority = 100): void {
    this.add('on_turn_end', registeredBy, fn, priority);
  }

  // ─────────── 触发 ───────────

  async fireOnTurnStart(ctx: OnTurnStartContext): Promise<void> {
    await this.fireVoid('on_turn_start', (fn) => (fn as OnTurnStartHook)(ctx));
  }

  /**
   * PreToolUse：可阻断 + 可改写 input。首个 reject=true 立即停链。
   */
  async firePreToolUse(ctx: PreToolUseContext): Promise<{ reject: boolean; reason?: string; finalInput: Record<string, unknown> }> {
    let finalInput = ctx.input;
    for (const { fn, registeredBy } of this.hooks.pre_tool_use) {
      try {
        const r = (await (fn as PreToolUseHook)({ ...ctx, input: finalInput })) as PreToolUseResult | void;
        if (r?.reject) {
          return { reject: true, reason: r.reason ?? `rejected by ${registeredBy}`, finalInput };
        }
        if (r?.patchedInput) finalInput = r.patchedInput;
      } catch (err) {
        this.logger.warn(`pre_tool_use hook ${registeredBy} threw: ${(err as Error).message}`);
      }
    }
    return { reject: false, finalInput };
  }

  async firePostToolUse(ctx: PostToolUseContext): Promise<void> {
    await this.fireVoid('post_tool_use', (fn) => (fn as PostToolUseHook)(ctx));
  }

  /** OnAssistantText：可改写文本（后续 hook 看到改后版本） */
  async fireOnAssistantText(ctx: OnAssistantTextContext): Promise<string> {
    let text = ctx.text;
    for (const { fn, registeredBy } of this.hooks.on_assistant_text) {
      try {
        const r = (await (fn as OnAssistantTextHook)({ ...ctx, text })) as OnAssistantTextResult | void;
        if (r?.patchedText !== undefined) text = r.patchedText;
      } catch (err) {
        this.logger.warn(`on_assistant_text hook ${registeredBy} threw: ${(err as Error).message}`);
      }
    }
    return text;
  }

  async firePostCompact(ctx: PostCompactContext): Promise<void> {
    await this.fireVoid('post_compact', (fn) => (fn as PostCompactHook)(ctx));
  }

  async fireOnTurnEnd(ctx: OnTurnEndContext): Promise<void> {
    await this.fireVoid('on_turn_end', (fn) => (fn as OnTurnEndHook)(ctx));
  }

  // ─────────── 内省 ───────────

  /** 列出某事件已注册的 hooks（admin / 调试用） */
  listHooks(event: HookEvent): Array<{ registeredBy: string; priority: number }> {
    return this.hooks[event].map((h) => ({ registeredBy: h.registeredBy, priority: h.priority }));
  }

  // ─────────── 内部 ───────────

  private add(event: HookEvent, registeredBy: string, fn: unknown, priority: number): void {
    this.hooks[event].push({ registeredBy, priority, fn });
    this.hooks[event].sort((a, b) => a.priority - b.priority);
  }

  /**
   * 串行触发"无返回值"事件，单 hook 抛错隔离。
   * 串行是有意的：用户 hook 可能依赖前序 hook 的副作用顺序（如 audit hook 应在
   * 业务 hook 之前落日志）。
   */
  private async fireVoid(
    event: HookEvent,
    run: (fn: unknown) => Promise<void> | void,
  ): Promise<void> {
    for (const { fn, registeredBy } of this.hooks[event]) {
      try {
        await run(fn);
      } catch (err) {
        this.logger.warn(`${event} hook ${registeredBy} threw: ${(err as Error).message}`);
      }
    }
  }
}
