/**
 * Claude Code Hook System - Event-based Plugin Hooks
 *
 * Features:
 * - Multiple hook types (pre/post events)
 * - Priority-based execution
 * - Async hook support
 * - Error handling and recovery
 * - Hook chaining
 */

import fs from 'fs/promises'
import path from 'path'
import { spawn } from 'child_process'

// ============================================================================
// TYPES AND INTERFACES
// ============================================================================

export type HookEvent =
  | 'UserPromptSubmit'
  | 'UserPromptSubmit-hook'
  | 'PreToolUse'
  | 'PostToolUse'
  | 'PreFileEdit'
  | 'PostFileEdit'
  | 'PreCommand'
  | 'PostCommand'
  | 'SessionStart'
  | 'SessionEnd'
  | 'PluginLoad'
  | 'PluginUnload'
  | 'Error'

export interface HookContext {
  event: HookEvent
  timestamp: string
  sessionId?: string
  messageId?: string
  data: Record<string, any>
}

export interface HookResult {
  success: boolean
  data?: any
  error?: string
  modifications?: {
    args?: any
    output?: any
    cancel?: boolean
  }
}

export interface HookDefinition {
  type: 'command' | 'module'
  command?: string
  module?: string
  handler?: string
  timeout?: number
  priority?: number
  condition?: string
  enabled?: boolean
}

export interface HookConfig {
  description?: string
  hooks: Record<string, { hooks: HookDefinition[] }>
}

// ============================================================================
// HOOK SYSTEM CLASS
// ============================================================================

export class HookSystem {
  private hooksFile: string
  private hooksDir: string
  private hooks: Map<HookEvent, HookDefinition[]> = new Map()
  private hookResults: Map<string, HookResult[]> = new Map()

  constructor(claudeDir: string = path.join(process.env.HOME || '', '.claude')) {
    this.hooksDir = path.join(claudeDir, 'hooks')
    this.hooksFile = path.join(claudeDir, 'hooks.json')
  }

  // ============================================================================
  // INITIALIZATION
  // ============================================================================

  async initialize(): Promise<void> {
    await this.ensureDirectories()
    await this.loadHooks()
  }

  private async ensureDirectories(): Promise<void> {
    try {
      await fs.mkdir(this.hooksDir, { recursive: true })
    } catch (error) {
      // Directory might already exist
    }
  }

  // ============================================================================
  // HOOK LOADING
  // ============================================================================

  async loadHooks(): Promise<void> {
    try {
      const config: HookConfig = JSON.parse(
        await fs.readFile(this.hooksFile, 'utf-8')
      )

      for (const [event, hookGroup] of Object.entries(config.hooks)) {
        this.hooks.set(event as HookEvent, hookGroup.hooks)
      }
    } catch (error) {
      // No hooks file or invalid JSON
      await this.saveHooks()
    }
  }

  async loadPluginHooks(pluginPath: string): Promise<void> {
    const hooksJsonPath = path.join(pluginPath, 'hooks', 'hooks.json')

    try {
      const config: HookConfig = JSON.parse(
        await fs.readFile(hooksJsonPath, 'utf-8')
      )

      for (const [event, hookGroup] of Object.entries(config.hooks)) {
        const existing = this.hooks.get(event as HookEvent) || []
        this.hooks.set(event as HookEvent, [...existing, ...hookGroup.hooks])
      }
    } catch {
      // No hooks file
    }
  }

  async saveHooks(): Promise<void> {
    const config: HookConfig = {
      description: 'Claude Code Hooks Configuration',
      hooks: {},
    }

    for (const [event, hooks] of this.hooks.entries()) {
      config.hooks[event] = { hooks }
    }

    await fs.writeFile(this.hooksFile, JSON.stringify(config, null, 2))
  }

  // ============================================================================
  // HOOK REGISTRATION
  // ============================================================================

  registerHook(event: HookEvent, hook: HookDefinition): void {
    const existing = this.hooks.get(event) || []
    existing.push(hook)
    this.hooks.set(event, existing.sort((a, b) => (b.priority || 0) - (a.priority || 0)))
  }

  unregisterHook(event: HookEvent, hookIdentifier: string): void {
    const existing = this.hooks.get(event) || []
    const filtered = existing.filter(
      (h) => h.command !== hookIdentifier && h.module !== hookIdentifier
    )
    this.hooks.set(event, filtered)
  }

  clearHooks(event?: HookEvent): void {
    if (event) {
      this.hooks.delete(event)
    } else {
      this.hooks.clear()
    }
  }

  // ============================================================================
  // HOOK EXECUTION
  // ============================================================================

  async executeHook(event: HookEvent, context: HookContext): Promise<HookResult[]> {
    const hooks = this.hooks.get(event) || []
    const results: HookResult[] = []

    for (const hook of hooks) {
      if (hook.enabled === false) continue

      try {
        const result = await this.executeSingleHook(hook, context)
        results.push(result)

        // Check if hook wants to cancel the operation
        if (result.modifications?.cancel) {
          break
        }
      } catch (error) {
        results.push({
          success: false,
          error: error instanceof Error ? error.message : String(error),
        })
      }
    }

    // Store results for later retrieval
    this.hookResults.set(`${event}-${context.timestamp}`, results)

    return results
  }

  private async executeSingleHook(
    hook: HookDefinition,
    context: HookContext
  ): Promise<HookResult> {
    const timeout = hook.timeout || 5000

    if (hook.type === 'command' && hook.command) {
      return await this.executeCommandHook(hook.command, context, timeout)
    } else if (hook.type === 'module' && hook.module) {
      return await this.executeModuleHook(hook.module, context, timeout)
    }

    throw new Error(`Unknown hook type`)
  }

  private async executeCommandHook(
    command: string,
    context: HookContext,
    timeout: number
  ): Promise<HookResult> {
    return new Promise((resolve, reject) => {
      const [cmd, ...args] = command.split(' ')

      const proc = spawn(cmd, args, {
        env: {
          ...process.env,
          HOOK_EVENT: context.event,
          HOOK_DATA: JSON.stringify(context.data),
          HOOK_TIMESTAMP: context.timestamp,
        },
        stdio: ['ignore', 'pipe', 'pipe'],
      })

      let stdout = ''
      let stderr = ''

      proc.stdout?.on('data', (data) => {
        stdout += data.toString()
      })

      proc.stderr?.on('data', (data) => {
        stderr += data.toString()
      })

      const timer = setTimeout(() => {
        proc.kill()
        reject(new Error(`Hook timeout after ${timeout}ms`))
      }, timeout)

      proc.on('close', (code) => {
        clearTimeout(timer)

        if (code === 0) {
          try {
            // Try to parse output as JSON for modifications
            const modifications = stdout.trim() ? JSON.parse(stdout) : undefined
            resolve({
              success: true,
              data: stdout,
              modifications,
            })
          } catch {
            resolve({
              success: true,
              data: stdout,
            })
          }
        } else {
          reject(new Error(`Hook failed: ${stderr || `exit code ${code}`}`))
        }
      })
    })
  }

  private async executeModuleHook(
    modulePath: string,
    context: HookContext,
    timeout: number
  ): Promise<HookResult> {
    // Dynamic import for TypeScript/JavaScript modules
    const startTime = Date.now()

    try {
      const module = await import(modulePath)
      const handler = module.default || module.hook || module.handler

      if (typeof handler !== 'function') {
        throw new Error(`Module ${modulePath} does not export a handler function`)
      }

      // Execute with timeout
      const result = await Promise.race([
        handler(context),
        new Promise<any>((_, reject) =>
          setTimeout(() => reject(new Error('Hook timeout')), timeout)
        ),
      ])

      return {
        success: true,
        data: result,
      }
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error),
      }
    }
  }

  // ============================================================================
  // UTILITY FUNCTIONS
  // ============================================================================

  getHookResults(event: HookEvent, timestamp: string): HookResult[] | undefined {
    return this.hookResults.get(`${event}-${timestamp}`)
  }

  getRegisteredHooks(event?: HookEvent): Map<HookEvent, HookDefinition[]> {
    if (event) {
      const hooks = this.hooks.get(event)
      return new Map(hooks ? [[event, hooks]] : [])
    }
    return this.hooks
  }

  async listHooksByEvent(event: HookEvent): Promise<HookDefinition[]> {
    return this.hooks.get(event) || []
  }
}

// ============================================================================
// HOOK BUILDER - Convenient API for Creating Hooks
// ============================================================================

export class HookBuilder {
  private hooks: HookDefinition[] = []

  command(cmd: string, options?: Partial<HookDefinition>): this {
    this.hooks.push({
      type: 'command',
      command: cmd,
      priority: options?.priority || 0,
      timeout: options?.timeout || 5000,
      enabled: options?.enabled !== false,
      condition: options?.condition,
    })
    return this
  }

  module(mod: string, options?: Partial<HookDefinition>): this {
    this.hooks.push({
      type: 'module',
      module: mod,
      priority: options?.priority || 0,
      timeout: options?.timeout || 5000,
      enabled: options?.enabled !== false,
      condition: options?.condition,
    })
    return this
  }

  build(): HookDefinition[] {
    return this.hooks
  }
}

// ============================================================================
// HELPER FUNCTIONS
// ============================================================================

export function createHookBuilder(): HookBuilder {
  return new HookBuilder()
}

export async function executeHooks(
  hookSystem: HookSystem,
  event: HookEvent,
  data: Record<string, any>
): Promise<HookResult[]> {
  const context: HookContext = {
    event,
    timestamp: Date.now().toString(),
    data,
  }

  return await hookSystem.executeHook(event, context)
}

// ============================================================================
// EXPORTS
// ============================================================================

export default HookSystem
